運行時類型信息使得你能夠在程序運行時發現和使用類型信息java
面向對象編程中基本的目的是:讓代碼只操做對基類的引用。
多態:程序員
import java.util.*; abstract class Shape { void draw() { System.out.println(this + ".draw()"); } abstract public String toString(); } class Circle extends Shape { public String toString() { return "Circle"; } } class Square extends Shape { public String toString() { return "Square"; } } class Triangle extends Shape { public String toString() { return "Triangle"; } } public class Shapes { public static void main(String[] args) { List<Shape> shapeList = Arrays.asList( new Circle(), new Square(), new Triangle() ); for(Shape shape : shapeList) shape.draw(); } } /* Output: Circle.draw() Square.draw() Triangle.draw() *///:~
Rtti基本使用形式:全部類型轉換都是在運行時進行正確性檢查的。在運行時識別一個對象的類型。編程
每當編寫而且編譯一個新類,就會產生一個Class對象。爲了生成這個類的對象,運行這個程序的Java虛擬機(JVM)將使用被稱爲"類加載器"的子系統。
類加載器子系統實際上能夠包含一條加載器鏈,可是隻有一個原生類加載器,它是JVM實現的一部分。原生類加載器加載的所謂的可信類,它們一般是從本地盤加載的。
全部類都在第一個使用時,動態加載到JVM中。
Java程序在它開始運行以前並不是被徹底加載,其各個部分是在必須時才加載的。
類加載器首先檢查這個類的class對象是否已經加載。若是沒有加載,默認的類加載器就會根據類名查找.class文件。設計模式
//: typeinfo/toys/ToyTest.java // Testing class Class. package toys; interface HasBatteries { } interface Waterproof { } interface Shoots { } class Toy { // Comment out the following default constructor // to see NoSuchMethodError from (*1*) Toy() { } Toy(int i) { } } class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots { FancyToy() { super(1); } } public class ToyTest { static void printInfo(Class cc) { System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]"); System.out.println("Simple name: " + cc.getSimpleName()); System.out.println("Canonical name : " + cc.getCanonicalName()); } public static void main(String[] args) { Class c = null; try { c = Class.forName("toys.FancyToy"); } catch (ClassNotFoundException e) { System.out.println("Can't find FancyToy"); System.exit(1); } printInfo(c); for (Class face : c.getInterfaces()) printInfo(face); Class up = c.getSuperclass(); Object obj = null; try { // Requires default constructor: obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("Cannot instantiate"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("Cannot access"); System.exit(1); } printInfo(obj.getClass()); } } /* Output: Class name: typeinfo.toys.FancyToy is interface? [false] Simple name: FancyToy Canonical name : typeinfo.toys.FancyToy Class name: typeinfo.toys.HasBatteries is interface? [true] Simple name: HasBatteries Canonical name : typeinfo.toys.HasBatteries Class name: typeinfo.toys.Waterproof is interface? [true] Simple name: Waterproof Canonical name : typeinfo.toys.Waterproof Class name: typeinfo.toys.Shoots is interface? [true] Simple name: Shoots Canonical name : typeinfo.toys.Shoots Class name: typeinfo.toys.Toy is interface? [false] Simple name: Toy Canonical name : typeinfo.toys.Toy *///:~
Java還提供了一個方法來生成對Class對象的引用,即便用類字面常量。
FancyToy.class;
類字面常量不只能夠應用於普通的類,也能夠應用於接口、數組、基本數據類型。
對於基本數據類型的包裝器類,還有一個標準字段TYPE。TYPE字段是一個引用,指向對應的基本數據類型的Class對象。
當使用.class建立對象的引用時,不會自動初始化該class對象。
爲了使用類而作的準備工做實際包含三個步驟:數組
//: typeinfo/ClassInitialization.java import java.util.*; class Initable { static final int staticFinal = 47;//編譯期常量,不須要進行初始化就能夠讀取 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); static { System.out.println("Initializing Initable"); } } class Initable2 { static int staticNonFinal = 147; static { System.out.println("Initializing Initable2"); } } class Initable3 { static int staticNonFinal = 74; static { System.out.println("Initializing Initable3"); } } public class ClassInitialization { public static Random rand = new Random(47); public static void main(String[] args) throws Exception { Class initable = Initable.class; System.out.println("After creating Initable ref"); // Does not trigger initialization: System.out.println(Initable.staticFinal); // Does trigger initialization: System.out.println(Initable.staticFinal2); // Does trigger initialization: System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName("Initable3"); System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } } /* Output: After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74 *///:~
若是一個static域不是final的,那麼在對它訪問時,老是要求在它被讀取前,先進行連接(分配存儲空間)和初始化(初始化存儲空間)網絡
Class引用老是指向某個Class對象,它能夠製造類的實例,幷包含可做用於這些實例的全部方法代碼。它還包含該類的靜態成員,所以,Class引用表達的就是它所指向的對象的確切類型,而該對象即是Class類的一個對象。
經過容許你對Class引用所指向的Class對象的類型進行限定,將它的類型變得更具體一些。less
public class GenericClassReferences { public static void main(String[] args) { Class intClass = int.class; Class<Integer> genericIntClass = int.class;//只能指向具體的類型 genericIntClass = Integer.class; // Same thing intClass = double.class; //genericIntClass = double.class; // Illegal } } ///:~
通配符:? 表示任何事物。dom
public class WildcardClassReferences { public static void main(String[] args) { Class<?> intClass = int.class; intClass = double.class; } } ///:~
Class<?>優於平凡得class,即使他們是等價得。Class<?>的好處表達你知道你選擇的是非具體的版本。
將通配符和extends關鍵字結合使用:ide
public class BoundedClassReferences { public static void main(String[] args) { Class<? extends Number> bounded = int.class; bounded = double.class; bounded = Number.class; // Or anything else derived from Number. } } ///:~
下面的例子,它存儲了一個引用,稍後又產生了一個List,填充這個List對象使用newInstance()方法:工具
import java.util.*; class CountedInteger { private static long counter; private final long id = counter++; public String toString() { return Long.toString(id); } } public class FilledList<T> { private Class<T> type; public FilledList(Class<T> type) { this.type = type; } public List<T> create(int nElements) { List<T> result = new ArrayList<T>(); try { for(int i = 0; i < nElements; i++) result.add(type.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } return result; } public static void main(String[] args) { FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class); System.out.println(fl.create(15)); } } /* Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] *///:~
newInstance()將返回對象的確切類型:
public class GenericToy { public static void main(String[] args) throws Exception { Class<B> b = B.class; System.out.println("a Name:" + b.getName()); B b1 = b.newInstance(); System.out.println(b); A a = (A)b.getSuperclass().newInstance(); System.out.println("getSuperclass.Name:"+a); A a1 = b.newInstance(); System.out.println(a1); Object o = b.getSuperclass(); System.out.println(o); } } class A{ static { System.out.println("AAAA"); } public String toString() { return "a"; } } class B extends A{ static { System.out.println("BBBB"); } public String toString() { return "b"; } }
Class引用的轉型語法cast()方法:接受參入參數對象,轉換爲引用類型
class Building {} class House extends Building {} public class ClassCasts { public static void main(String[] args) { Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); h = (House)b; // ... or just do this. } } ///:~
Class.asSubclass():容許你將一個類對象轉型爲更加具體的類型。
咱們已知的RTTI形式包括:
instanceof只可將其與命名空間類型進行比較,而不能與Class對象做比較。
使用工廠方法設計模式,將對象的建立工做交給類本身去完成,工廠方法能夠被多態的調用,從而爲你建立恰當類型的對象。
使用工廠建立對象:
//: typeinfo/RegisteredFactories.java // Registering Class Factories in the base class. import factory.*; import java.util.*; class Part { public String toString() { return getClass().getSimpleName(); } static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>(); static { // Collections.addAll() gives an "unchecked generic // array creation ... for varargs parameter" warning. partFactories.add(new FuelFilter.Factory()); partFactories.add(new AirFilter.Factory()); partFactories.add(new CabinAirFilter.Factory()); partFactories.add(new OilFilter.Factory()); partFactories.add(new FanBelt.Factory()); partFactories.add(new PowerSteeringBelt.Factory()); partFactories.add(new GeneratorBelt.Factory()); } private static Random rand = new Random(47); public static Part createRandom() { int n = rand.nextInt(partFactories.size()); return partFactories.get(n).create(); } } class Filter extends Part {} class FuelFilter extends Filter { // Create a Class Factory for each specific type: public static class Factory implements factory.Factory<FuelFilter> { public FuelFilter create() { return new FuelFilter(); } } } class AirFilter extends Filter { public static class Factory implements factory.Factory<AirFilter> { public AirFilter create() { return new AirFilter(); } } } class CabinAirFilter extends Filter { public static class Factory implements factory.Factory<CabinAirFilter> { public CabinAirFilter create() { return new CabinAirFilter(); } } } class OilFilter extends Filter { public static class Factory implements factory.Factory<OilFilter> { public OilFilter create() { return new OilFilter(); } } } class Belt extends Part {} class FanBelt extends Belt { public static class Factory implements factory.Factory<FanBelt> { public FanBelt create() { return new FanBelt(); } } } class GeneratorBelt extends Belt { public static class Factory implements factory.Factory<GeneratorBelt> { public GeneratorBelt create() { return new GeneratorBelt(); } } } class PowerSteeringBelt extends Belt { public static class Factory implements factory.Factory<PowerSteeringBelt> { public PowerSteeringBelt create() { return new PowerSteeringBelt(); } } } public class RegisteredFactories { public static void main(String[] args) { for(int i = 0; i < 10; i++) System.out.println(Part.createRandom()); } } /* Output: GeneratorBelt CabinAirFilter GeneratorBelt AirFilter PowerSteeringBelt CabinAirFilter FuelFilter PowerSteeringBelt PowerSteeringBelt FuelFilter *///:~
在查詢類型信息時,以instanceof的形式與直接比較Class對象有一個很重要的差異,下面看看這些差異:
//: typeinfo/FamilyVsExactType.java // The difference between instanceof and class class Base {} class Derived extends Base {} public class FamilyVsExactType { static void test(Object x) { System.out.println("Testing x of type " + x.getClass()); System.out.println("x instanceof Base " + (x instanceof Base)); System.out.println("x instanceof Derived "+ (x instanceof Derived)); System.out.println("Base.isInstance(x) "+ Base.class.isInstance(x)); System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x)); System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class)); System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class)); System.out.println("x.getClass().equals(Base.class)) "+ (x.getClass().equals(Base.class))); System.out.println("x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args) { test(new Base()); test(new Derived()); } } /* Output: Testing x of type class typeinfo.Base x instanceof Base true x instanceof Derived false Base.isInstance(x) true Derived.isInstance(x) false x.getClass() == Base.class true x.getClass() == Derived.class false x.getClass().equals(Base.class)) true x.getClass().equals(Derived.class)) false Testing x of type class typeinfo.Derived x instanceof Base true x instanceof Derived true Base.isInstance(x) true Derived.isInstance(x) true x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true *///:~
在編譯時,編譯器必須知道所要經過RTTI來處理的類。
反射提供了一種機制——用來檢查可用的方法,並返回方法名。
人們想要在運行時獲取類的信息的另外一個動機,但願在跨網絡的平臺上建立和運行對象的能力。這被稱爲遠程方法調用(RMI),它容許將一個Java程序分佈到多臺機器上。
Class類與java.lang.reflect類庫一塊兒對反射概念進行了支持。這些類型的對象都是又JVM在運行時建立的,用以表示未知類裏對應的成員。
當經過反射與一個未知類型的對象打交道時,JVM只是簡單的檢查這個對象,看它屬於哪一個特定的類。在用它作其餘事情以前必須先加載那個類的Class對象。所以,那個類的.Class文件對於JVM來講必須是可獲取的:要麼在本地機器上,要麼能夠經過網絡獲取。因此RTTI和反射之間真正的區別只在於du:對RTTI來講,編譯器在編譯時打開和檢查.class文件,對於反射來講:.class在編譯時是不可獲取的,全部在運行時打開和檢查.class文件。
反射機制提供了一種方法,使咱們可以編寫能夠自動展現完整接口的簡單工具:
代理是基本的設計模式之一,它是你爲了提供額外的或不一樣的操做,而插入的用來代替實際對象的對象。
interface Interface { void doSomething(); void somethingElse(String arg); } class RealObject implements Interface { public void doSomething() { System.out.println("doSomething"); } public void somethingElse(String arg) { System.out.println("somethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; public SimpleProxy(Interface proxied) { this.proxied = proxied; } public void doSomething() { System.out.println("SimpleProxy doSomething"); proxied.doSomething(); } public void somethingElse(String arg) { System.out.println("SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); } } class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } }
Java的動態代理能夠動態建立並動態地處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器上,它的工做是揭示調用的類型並肯定相應的對策。
import java.lang.reflect.*; class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); if(args != null) for(Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); // Insert a proxy and call again: Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{ Interface.class }, new DynamicProxyHandler(real)); consumer(proxy); } }
動態代理能夠將全部調用重定向到調用處理器,所以一般會向調用處理器的構造器傳遞一個實際對象的引用,從而使得調用處理器在執行其中介任務時,能夠將請求轉發。
引入空對象的思想是頗有用的,它能夠接收傳遞給它的所表明的對象的消息,可是將返回表示爲實際上並不存在的任何真實對象的值。經過這種方式,你能夠假設全部的對象都是有效的,而沒必要浪費時間去檢查null。
即便空對象能夠響應實際對象能夠響應的全部消息,任須要測試是否爲空:
public interface Operation { String description(); void command(); }
class Person { public final String first; public final String last; public final String address; // etc. public Person(String first, String last, String address){ this.first = first; this.last = last; this.address = address; } public String toString() { return "Person: " + first + " " + last + " " + address; } public static class NullPerson extends Person implements Null { private NullPerson() { super("None", "None", "None"); } public String toString() { return "NullPerson"; } } public static final Person NULL = new NullPerson(); }
class Position { private String title; private Person person; public Position(String jobTitle, Person employee) { title = jobTitle; person = employee; if(person == null) person = Person.NULL; } public Position(String jobTitle) { title = jobTitle; person = Person.NULL; } public String getTitle() { return title; } public void setTitle(String newTitle) { title = newTitle; } public Person getPerson() { return person; } public void setPerson(Person newPerson) { person = newPerson; if(person == null) person = Person.NULL; } public String toString() { return "Position: " + title + " " + person; } }
//: typeinfo/Staff.java import java.util.*; public class Staff extends ArrayList<Position> { public void add(String title, Person person) { add(new Position(title, person)); } public void add(String... titles) { for(String title : titles) add(new Position(title)); } public Staff(String... titles) { add(titles); } public boolean positionAvailable(String title) { for(Position position : this) if(position.getTitle().equals(title) && position.getPerson() == Person.NULL) return true; return false; } public void fillPosition(String title, Person hire) { for(Position position : this) if(position.getTitle().equals(title) && position.getPerson() == Person.NULL) { position.setPerson(hire); return; } throw new RuntimeException( "Position " + title + " not available"); } public static void main(String[] args) { Staff staff = new Staff("President", "CTO", "Marketing Manager", "Product Manager", "Project Lead", "Software Engineer", "Software Engineer", "Software Engineer", "Software Engineer", "Test Engineer", "Technical Writer"); staff.fillPosition("President", new Person("Me", "Last", "The Top, Lonely At")); staff.fillPosition("Project Lead", new Person("Janet", "Planner", "The Burbs")); if(staff.positionAvailable("Software Engineer")) staff.fillPosition("Software Engineer", new Person("Bob", "Coder", "Bright Light City")); System.out.println(staff); } } /* Output: [Position: President Person: Me Last The Top, Lonely At, Position: CTO NullPerson, Position: Marketing Manager NullPerson, Position: Product Manager NullPerson, Position: Project Lead Person: Janet Planner The Burbs, Position: Software Engineer Person: Bob Coder Bright Light City, Position: Software Engineer NullPerson, Position: Software Engineer NullPerson, Position: Software Engineer NullPerson, Position: Test Engineer NullPerson, Position: Technical Writer NullPerson] *///:~
使用命令模式:
//: typeinfo/Robot.java import java.util.*; public interface Robot { String name(); String model(); List<Operation> operations(); class Test { public static void test(Robot r) { if(r instanceof Null) System.out.println("[Null Robot]"); System.out.println("Robot name: " + r.name()); System.out.println("Robot model: " + r.model()); for(Operation operation : r.operations()) { System.out.println(operation.description()); operation.command(); } } } } ///:~
//: typeinfo/SnowRemovalRobot.java import java.util.*; public class SnowRemovalRobot implements Robot { private String name; public SnowRemovalRobot(String name) {this.name = name;} public String name() { return name; } public String model() { return "SnowBot Series 11"; } public List<Operation> operations() { return Arrays.asList( new Operation() { public String description() { return name + " can shovel snow"; } public void command() { System.out.println(name + " shoveling snow"); } }, new Operation() { public String description() { return name + " can chip ice"; } public void command() { System.out.println(name + " chipping ice"); } }, new Operation() { public String description() { return name + " can clear the roof"; } public void command() { System.out.println(name + " clearing roof"); } } ); } public static void main(String[] args) { Robot.Test.test(new SnowRemovalRobot("Slusher")); } } /* Output: Robot name: Slusher Robot model: SnowBot Series 11 Slusher can shovel snow Slusher shoveling snow Slusher can chip ice Slusher chipping ice Slusher can clear the roof Slusher clearing roof *///:~
//: typeinfo/NullRobot.java // Using a dynamic proxy to create a Null Object. import java.lang.reflect.*; import java.util.*; class NullRobotProxyHandler implements InvocationHandler { private String nullName; private Robot proxied = new NRobot(); NullRobotProxyHandler(Class<? extends Robot> type) { nullName = type.getSimpleName() + " NullRobot"; } private class NRobot implements Null, Robot { public String name() { return nullName; } public String model() { return nullName; } public List<Operation> operations() { return Collections.emptyList(); } } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxied, args); } } public class NullRobot { public static Robot newNullRobot(Class<? extends Robot> type) { return (Robot)Proxy.newProxyInstance( NullRobot.class.getClassLoader(), new Class[]{ Null.class, Robot.class }, new NullRobotProxyHandler(type)); } public static void main(String[] args) { Robot[] bots = { new SnowRemovalRobot("SnowBee"), newNullRobot(SnowRemovalRobot.class) }; for(Robot bot : bots) Robot.Test.test(bot); } } /* Output: Robot name: SnowBee Robot model: SnowBot Series 11 SnowBee can shovel snow SnowBee shoveling snow SnowBee can chip ice SnowBee chipping ice SnowBee can clear the roof SnowBee clearing roof [Null Robot] Robot name: SnowRemovalRobot NullRobot Robot model: SnowRemovalRobot NullRobot *///:~
空對象的邏輯變體是模擬對象和樁。模擬對象和樁都只是假扮能夠傳遞實際信息的存活對象,而不是像空對象那樣能夠稱爲null的一種更加智能化的替代品。
interface關鍵字的一種重要目標就是容許程序員隔離構建,進而下降耦合性。經過類型信息,這種耦合性仍是會傳播出去——接口並不是是對解耦的一種無懈可擊的保障。
package interfacea; public interface A { void f(); }
import interfacea.*; class B implements A { public void f() {} public void g() {} } public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); // a.g(); // Compile error System.out.println(a.getClass().getName()); if(a instanceof B) { B b = (B)a; b.g(); } } }
最簡單的方式是對實現使用包訪問權限,這樣在包外部的客戶端就不能看見它了。
package packageaccess; import interfacea.*; class C implements A { public void f() { System.out.println("public C.f()"); } public void g() { System.out.println("public C.g()"); } void u() { System.out.println("package C.u()"); } protected void v() { System.out.println("protected C.v()"); } private void w() { System.out.println("private C.w()"); } } public class HiddenC { public static A makeA() { return new C(); } }
這裏makeA返回C類型,但在包在並不能使用到C。可是反射卻仍舊能夠調用:
import interfacea.*; import packageaccess.*; import java.lang.reflect.*; public class HiddenImplementation { public static void main(String[] args) throws Exception { A a = HiddenC.makeA(); a.f(); System.out.println(a.getClass().getName()); // Compile error: cannot find symbol 'C': /* if(a instanceof C) { C c = (C)a; c.g(); } */ // Oops! Reflection still allows us to call g(): callHiddenMethod(a, "g"); // And even methods that are less accessible! callHiddenMethod(a, "u"); callHiddenMethod(a, "v"); callHiddenMethod(a, "w"); } static void callHiddenMethod(Object a, String methodName) throws Exception { Method g = a.getClass().getDeclaredMethod(methodName); g.setAccessible(true); g.invoke(a); } } /* Output: public C.f() typeinfo.packageaccess.C public C.g() package C.u() protected C.v() private C.w() *///:~
即便是內部類,反思仍舊能夠調用到:
import interfacea.*; class InnerA { private static class C implements A { public void f() { System.out.println("public C.f()"); } public void g() { System.out.println("public C.g()"); } void u() { System.out.println("package C.u()"); } protected void v() { System.out.println("protected C.v()"); } private void w() { System.out.println("private C.w()"); } } public static A makeA() { return new C(); } } public class InnerImplementation { public static void main(String[] args) throws Exception { A a = InnerA.makeA(); a.f(); System.out.println(a.getClass().getName()); // Reflection still gets into the private class: HiddenImplementation.callHiddenMethod(a, "g"); HiddenImplementation.callHiddenMethod(a, "u"); HiddenImplementation.callHiddenMethod(a, "v"); HiddenImplementation.callHiddenMethod(a, "w"); } }
匿名類也同樣:
import interfacea.*; class AnonymousA { public static A makeA() { return new A() { public void f() { System.out.println("public C.f()"); } public void g() { System.out.println("public C.g()"); } void u() { System.out.println("package C.u()"); } protected void v() { System.out.println("protected C.v()"); } private void w() { System.out.println("private C.w()"); } }; } } public class AnonymousImplementation { public static void main(String[] args) throws Exception { A a = AnonymousA.makeA(); a.f(); System.out.println(a.getClass().getName()); // Reflection still gets into the anonymous class: HiddenImplementation.callHiddenMethod(a, "g"); HiddenImplementation.callHiddenMethod(a, "u"); HiddenImplementation.callHiddenMethod(a, "v"); HiddenImplementation.callHiddenMethod(a, "w"); } } /* Output: public C.f() AnonymousA$1 public C.g() package C.u() protected C.v() private C.w() *///:~