读Java编程思想随笔の类型信息
运行时类型信息使得你可以在程序运行时发现和使用类型信息。
它使你从只能在编译期执行面向类型的操作的禁锢中解脱出来,并且可以使用某些非常强大的程序。对于RTTI的需要,揭示了面向对象的设计中许多有趣的问题,同时也提出了如何组织程序的问题。
如何让我们在执行程序时识别对象和类的信息,主要有两种方式:一,“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我么在运行时发现和使用类的信息。
为什么需要RTTI
这是一个很熟悉的例子,它使用了多态的类层次结构。最通用的类型(泛型)是基类Shape,而派生出的具体有Circle、Square和Triangle。
这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程中基本的目的是:让代码只操作对基类的引用。这样,如果要添加一个新类来扩展程序,就不会影响到原来的代码。在这个例子中Shape动态绑定了draw()方法,目的就是为了让客户端程序使用泛化的Shape引用来调用draw()方法。draw()在所有派生类中都会被覆盖,并且由于它是动态绑定的,所以即使是通过泛化的Shape引用来调用,也能产生正确的行为。这就是多态。
因此,通常会创建一个具体的对象(Circle、Square或Triangle),把它向上转型为Shape,并在后面的程序中使用匿名的Shape引用。
1 public class Shapes { 2 public static void main(String[] args) { 3 List<Shape> shapeList = Arrays.asList(new Circle(),new Triangle(),new Square()); 4 for (Shape shape:shapeList){ 5 shape.draw(); 6 } 7 } 8 } 9 abstract class Shape{ 10 void draw (){ 11 System.out.println(this+".draw()"); 12 } 13 abstract public String toString();//强制继承者复写该方法 14 } 15 class Circle extends Shape{ 16 17 @Override 18 public String toString() { 19 return "Circle"; 20 } 21 } 22 class Triangle extends Shape{ 23 24 @Override 25 public String toString() { 26 return "Triangle"; 27 } 28 } 29 class Square extends Shape{ 30 31 @Override 32 public String toString() { 33 return "Square"; 34 } 35 } 36 /** 37 * 这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程基本目的是:让代码只操作基类的引用, 38 * 这样,如果要添加一个新类来扩展程序,就不会影响原来的代码。在这个示例中,Shape接口动态绑定了draw()方法, 39 * 目的就是让客户端程序员使用泛化的Shape引用来调用draw()。draw()在所有派生类里都会被覆盖,并且由于它是动态 40 * 绑定的,所以即使通过泛化的Shape引用来调用,也能产生正确的行为。这就是多态。 41 * 42 * 在这个例子中,当把Shape对象放入List<Shape>数组时它会向上转型。但在向上转型为Shape类型时它也丢失了原来具体 43 * 的类型。对于数组而言,它们只是Shape类的对象。 44 * 45 * 当从数组中取出元素时,实际上,这种容器它将所有对象都当做Object持有,并会自动转型为Shape。这是RTTI最基本的 46 * 使用形式,因为在Java中,所有类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个 47 * 对象的类型。 48 * 49 * 在这个例子中,RTTI类型转换并不彻底;Object被转型为Shape,而不是转型为Circle\Triangle\Square,这是因为目前 50 * 我们只知道这个List<Shape>保存的都是Shape。在编译时,将由容器或泛型系统来确保这一点;而在运行时,由类型转换 51 * 操作来确保这点。 52 * 53 * */
Class对象
要了解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由Class对象这一特殊对象来完成的,它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的常规对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。
类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更确切的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的虚拟机将使用被称为“类加载器”的子系统。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求,那么你有一种方式可以挂接额外的类加载器。
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当作类的静态成员的引用。
类加载器首先确认类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破环,并且不包含不良Java代码。一旦某个类的Class对象被载入内存,它就被用来创建这个类的多有对象。
1 public class SweetShop { 2 public static void main(String[] args) { 3 System.out.println("inside main"); 4 new Candy(); 5 System.out.println("After creating Candy"); 6 try { 7 //new Gum(); 8 Class.forName("Gum"); 9 } catch (ClassNotFoundException e) { 10 //e.printStackTrace(); 11 System.out.println("Coundnt find Gum"); 12 } 13 System.out.println("After Class.forName(\"Gum\")"); 14 new Cookie(); 15 System.out.println("After creating Cookie"); 16 } 17 } 18 class Candy{ 19 static { 20 System.out.println("Loading Candy"); 21 } 22 } 23 class Gum{ 24 static { 25 System.out.println("Loading Gum"); 26 } 27 } 28 class Cookie{ 29 static { 30 System.out.println("Loading Cookie"); 31 } 32 } 33 // inside main 34 // Loading Candy 35 // After creating Candy 36 // Coundnt find Gum 37 // After Class.forName("Gum") 38 // Loading Cookie 39 // After creating Cookie 40 41 /** 42 * 要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是有 43 * 称为Class对象的特殊对象完成的,它包含了与类有关的信息。事实上,Class对象用来创建类的 44 * 常规对象。Java使用Class对象执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类 45 * 还拥有大量的使用RTTI的其他方式。 46 * 47 * 类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生 48 * 一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行 49 * 这个程序的Java虚拟机将使用被称为类加载器的子系统。 50 * 51 * 类加载器子系统实际上包含了一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。 52 * 原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地磁盘加载的。在这条链中 53 * ,通常不需要添加额外的类加载器。 54 * 55 * 所有的类都是在对其第一次使用时,动态加载到JVM中,当程序创建第一个对类的静态成员的引用时 56 * 就会加载这个类。这个证明构造器也是累的静态方法,即使在构造器之前并没有使用static关键字 57 * 。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。 58 * 59 * 因此,Java程序在被运行之前并未被完全加载,其各个部分是必需时才加载的。这一点与许多传统语言 60 * 都不同。 61 * 62 * 类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找 63 * .class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良代码 64 * 65 * 66 * */
Class对象一些有用的方法
1 interface HasBatteries{} 2 interface Waterproof{} 3 interface Shoots{} 4 class Toy{ 5 Toy(){} 6 Toy(int i){} 7 } 8 class FancyToy extends Toy implements HasBatteries,Waterproof,Shoots{ 9 FancyToy(){super(1);} 10 } 11 public class ToyTest { 12 static void printInfo(Class cc){ 13 System.out.println("Class name:"+cc.getName()+" is interface?["+cc.isInterface()+"]"); 14 System.out.println("Simple name:"+cc.getSimpleName()); 15 System.out.println("Canonical name:"+cc.getCanonicalName()); 16 } 17 18 public static void main(String[] args) { 19 Class c = null; 20 try { 21 c = Class.forName("com.qiqi.typeinfo.FancyToy"); 22 } catch (ClassNotFoundException e) { 23 // e.printStackTrace(); 24 System.out.println("Cant find FancyToy"); 25 System.exit(1); 26 } 27 printInfo(c); 28 for (Class face:c.getInterfaces()){ 29 printInfo(face); 30 } 31 Class up = c.getSuperclass(); 32 Object obj = null; 33 try { 34 obj = up.newInstance(); 35 } catch (InstantiationException e) { 36 System.out.println("Cannot instantiate"); 37 System.exit(1); 38 } catch (IllegalAccessException e) { 39 System.out.println("Cannot access"); 40 System.exit(1); 41 } 42 printInfo(obj.getClass()); 43 44 } 45 } 46 // Class name:com.qiqi.typeinfo.FancyToy is interface?[false] 47 // Simple name:FancyToy 48 // Canonical name:com.qiqi.typeinfo.FancyToy 49 // Class name:com.qiqi.typeinfo.HasBatteries is interface?[true] 50 // Simple name:HasBatteries 51 // Canonical name:com.qiqi.typeinfo.HasBatteries 52 // Class name:com.qiqi.typeinfo.Waterproof is interface?[true] 53 // Simple name:Waterproof 54 // Canonical name:com.qiqi.typeinfo.Waterproof 55 // Class name:com.qiqi.typeinfo.Shoots is interface?[true] 56 // Simple name:Shoots 57 // Canonical name:com.qiqi.typeinfo.Shoots 58 // Class name:com.qiqi.typeinfo.Toy is interface?[false] 59 // Simple name:Toy 60 // Canonical name:com.qiqi.typeinfo.Toy
类字面常量
Java还提供了另一种方法来生成对Class对象的引用,即使用类字面变量。对上述程序来说就像这样:FancyToy.class;这样做不仅简单,而且更加安全,因为它在编译时就会受到检查(因此它不需要置于try语句块中)。并且它根除了对forName()的调用,所以也更高效。
类字面变量不仅可以用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型Class对象。
我们建议使用.class的形式,以保持与普通类的一致性。
注意,当使用.class来创建对Class对象的引用时,不会自动地初始化该Class对象。为了使用类而作的准备工作实际包含三个步骤:
1、加载,这是由类加载器执行的(通常在classpath所指定的路径中查找,但这并非是必需的)。该步骤将查找字节码,并从这些字节码中创建一个Class对象;
2、链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类传几个I安的对其他类的所有引用;
3、初始化,如果该类具有超类,则对其初始化、执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行
1 class Initable { 2 static final int staticFinal = 47; 3 static final int staticFinal2 = 4 ClassInitialization.rand.nextInt(1000); 5 static { 6 System.out.println("Initializing Initable"); 7 } 8 } 9 10 class Initable2 { 11 static int staticNonFinal = 147; 12 static { 13 System.out.println("Initializing Initable2"); 14 } 15 } 16 17 class Initable3 { 18 static int staticNonFinal = 74; 19 static { 20 System.out.println("Initializing Initable3"); 21 } 22 } 23 24 public class ClassInitialization { 25 public static Random rand = new Random(47); 26 public static void main(String[] args) throws Exception { 27 Class initable = Initable.class; 28 System.out.println("After creating Initable ref"); 29 // Does not trigger initialization: 30 System.out.println(Initable.staticFinal); 31 // Does trigger initialization: 32 System.out.println(Initable.staticFinal2);//强制进行初始化,因为staticFinal2不是一个编译器常量 33 // Does trigger initialization: 34 System.out.println(Initable2.staticNonFinal); 35 Class initable3 = Class.forName("Initable3");//写全路径,并try catch 36 System.out.println("After creating Initable3 ref"); 37 System.out.println(Initable3.staticNonFinal); 38 } 39 } /* Output: 40 After creating Initable ref 41 47 42 Initializing Initable 43 258 44 Initializing Initable2 45 147 46 Initializing Initable3 47 After creating Initable3 ref 48 74 49 *///:~
初始化有效地实现了尽可能的“惰性”。从对initable引用的创建中可以看到,仅使用.class方法来获得对类的引用不会引发初始化。但是,为了产生Class引用,Class.forName()立即就进行了初始化,就像在对initable3引用的创建中所看到的。
如果一个static final值是一个编译器常量,就像initable.staticFinal那样,那么这个值不需要对Initable类进行初始化就可以被读取。但是,如果只是将一个域设置为static和final,还不足以确保这种行为。例如,对initable.staticFinal2的访问将强制进行类的初始化,因为它不是一个编译器常量。
如果一个static域不是final的,那么在对它访问时,总是要求它在被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),就像对initable2.staticNonFinal访问所看到的那样。
泛化的Class引用
Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
但是,Java SE5的设计者们看准了机会,将它的类型变的更具体了一些,而这是通过允许你对Class引用所指向的Class对象的类型进行限定而实现的,这里用到了泛型语法。
1 public class GenericClassReferences { 2 3 public static void main(String[] args) { 4 Class intClass = int.class; 5 Class<Integer> genericIntClass = int.class; 6 genericIntClass = Integer.TYPE; 7 intClass = double.class; 8 //加入了泛化的Class引用 9 //genericIntClass = double.class; 10 } 11 }
普通的类引用不会产生警告信息,你可以看到,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
为了在使用泛化的Class引用时放松限制。可以使用通配符,它是Java泛型的一部分。通配符就是“?”,表示“任何事物”。看下面示例。
1 public class WildcardClassReferences { 2 public static void main(String[] args) { 3 Class<?> intClass = int.class; 4 intClass = double.class; 5 } 6 }
Class<?>的好处是它表示你并非是碰巧或者由于疏忽,而使用了一个非具体的类引用,你就是选择了非具体的版本。
为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。
1 public class BoundedClassReferences { 2 public static void main(String[] args) { 3 Class<? extends Number> bounded = int.class; 4 bounded = double.class; 5 bounded = Number.class; 6 //or anything else derived from Number 7 } 8 }
下面的示例使用了泛型类语法。它存储了一个类引用,稍候又产生了一个List,填充这个List的对象是使用newInstance()方法,通过该引用生成的
1 public class FilledList<T> { 2 private Class<T> type; 3 public FilledList(Class<T> type){this.type = type;} 4 public List<T> create(int nElement){ 5 List<T> list = new ArrayList<T>(); 6 7 for (int i=0;i<nElement;i++){ 8 try { 9 list.add(type.newInstance());//引用实例化 10 } catch (InstantiationException e) { 11 e.printStackTrace(); 12 } catch (IllegalAccessException e) { 13 e.printStackTrace(); 14 } 15 } 16 return list; 17 } 18 19 public static void main(String[] args) { 20 FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class); 21 System.out.println(fl.create(15)); 22 } 23 24 } 25 class CountedInteger{ 26 private static long counter; 27 private final long id = counter++; 28 @Override 29 public String toString() { 30 return Long.toString(id); 31 } 32 } 33 //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
类型转换前先做检查
RTTI形式包括:
1)传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
2)代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
3)使用关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
我们需要一种方法,通过它可以随机地创建不同类型的宠物,并且还可以创建宠物数组和List。
1 public abstract class PetCreator { 2 private Random rand = new Random(47); 3 // The List of the different types of Pet to create: 4 public abstract List<Class<? extends Pet>> types(); 5 public Pet randomPet() { // Create one random Pet 6 int n = rand.nextInt(types().size()); 7 try { 8 return types().get(n).newInstance(); 9 } catch(InstantiationException e) { 10 throw new RuntimeException(e); 11 } catch(IllegalAccessException e) { 12 throw new RuntimeException(e); 13 } 14 } 15 public Pet[] createArray(int size) { 16 Pet[] result = new Pet[size]; 17 for(int i = 0; i < size; i++) 18 result[i] = randomPet(); 19 return result; 20 } 21 public ArrayList<Pet> arrayList(int size) { 22 ArrayList<Pet> result = new ArrayList<Pet>(); 23 Collections.addAll(result, createArray(size)); 24 return result; 25 } 26 } ///:~
抽象的types()方法在导出类中实现,以获取由Class对象构成的List。注意,其中类的类型被指定为“任何从Pet导出的类”,因此,newInstance()不需要转型就可以产生Pet。randomPet()随机地产生List中的索引,并使用被选取的Class对象,通过Class.newInstance()来生成该类的新实例。createArray()方法使用randomPet()来填充数组,而arrayList()方法使用的则是createArray()。
下面是使用forName()的一个具体实现

1 public class Individual implements Comparable<Individual> { 2 private static long counter = 0; 3 private final long id = counter++; 4 private String name; 5 public Individual(String name) { this.name = name; } 6 // 'name' is optional: 7 public Individual() {} 8 public String toString() { 9 return getClass().getSimpleName() + 10 (name == null ? "" : " " + name); 11 } 12 public long id() { return id; } 13 public boolean equals(Object o) { 14 return o instanceof Individual && 15 id == ((Individual)o).id; 16 } 17 public int hashCode() { 18 int result = 17; 19 if(name != null) 20 result = 37 * result + name.hashCode(); 21 result = 37 * result + (int)id; 22 return result; 23 } 24 public int compareTo(Individual arg) { 25 // Compare by class name first: 26 String first = getClass().getSimpleName(); 27 String argFirst = arg.getClass().getSimpleName(); 28 int firstCompare = first.compareTo(argFirst); 29 if(firstCompare != 0) 30 return firstCompare; 31 if(name != null && arg.name != null) { 32 int secondCompare = name.compareTo(arg.name); 33 if(secondCompare != 0) 34 return secondCompare; 35 } 36 return (arg.id < id ? -1 : (arg.id == id ? 0 : 1)); 37 } 38 } ///:~ 39 public class Pet extends Individual { 40 public Pet(String name) { super(name); } 41 public Pet() { super(); } 42 } ///:~ 43 public abstract class PetCreator { 44 private Random rand = new Random(47); 45 // The List of the different types of Pet to create: 46 public abstract List<Class<? extends Pet>> types(); 47 public Pet randomPet() { // Create one random Pet 48 int n = rand.nextInt(types().size()); 49 try { 50 return types().get(n).newInstance(); 51 } catch(InstantiationException e) { 52 throw new RuntimeException(e); 53 } catch(IllegalAccessException e) { 54 throw new RuntimeException(e); 55 } 56 } 57 public Pet[] createArray(int size) { 58 Pet[] result = new Pet[size]; 59 for(int i = 0; i < size; i++) 60 result[i] = randomPet(); 61 return result; 62 } 63 public ArrayList<Pet> arrayList(int size) { 64 ArrayList<Pet> result = new ArrayList<Pet>(); 65 Collections.addAll(result, createArray(size)); 66 return result; 67 } 68 } ///:~ 69 public class ForNameCreator extends PetCreator { 70 private static List<Class<? extends Pet>> types = 71 new ArrayList<Class<? extends Pet>>(); 72 // Types that you want to be randomly created: 73 private static String[] typeNames = { 74 "typeinfo.pets.Mutt", 75 "typeinfo.pets.Pug", 76 "typeinfo.pets.EgyptianMau", 77 "typeinfo.pets.Manx", 78 "typeinfo.pets.Cymric", 79 "typeinfo.pets.Rat", 80 "typeinfo.pets.Mouse", 81 "typeinfo.pets.Hamster" 82 }; 83 @SuppressWarnings("unchecked") 84 private static void loader() { 85 try { 86 for(String name : typeNames) 87 types.add( 88 (Class<? extends Pet>)Class.forName(name)); 89 } catch(ClassNotFoundException e) { 90 throw new RuntimeException(e); 91 } 92 } 93 static { loader(); } 94 public List<Class<? extends Pet>> types() {return types;} 95 } ///:~
loader()方法用Class.forName()创建了Class对象的List,这可能会产生ClassNotFoundException异常,这么做事有意义的,因为你传递给它的是一个在编译期无法验证的String。由于Pet对象在typeinfo包中,因此必须使用包名来引用这些类。
为了产生具有实际类型的Class对象的List,必须使用转型,这会产生编译期警告。loader()方法被单独定义,然后被置于一个静态初始化子句中,因为@SuppressWarnings("unchecked")注解不能直接置于静态初始化子句之上。
使用instanceof来对Pet进行计数
1 public class PetCount { 2 static class PetCounter extends HashMap<String,Integer> { 3 public void count(String type) { 4 Integer quantity = get(type); 5 if(quantity == null) 6 put(type, 1); 7 else 8 put(type, quantity + 1); 9 } 10 } 11 public static void 12 countPets(PetCreator creator) { 13 PetCounter counter= new PetCounter(); 14 for(Pet pet : creator.createArray(20)) { 15 // List each individual pet: 16 System.out.println(pet.getClass().getSimpleName() + " "); 17 if(pet instanceof Pet) 18 counter.count("Pet"); 19 if(pet instanceof Dog) 20 counter.count("Dog"); 21 if(pet instanceof Mutt) 22 counter.count("Mutt"); 23 if(pet instanceof Pug) 24 counter.count("Pug"); 25 if(pet instanceof Cat) 26 counter.count("Cat"); 27 if(pet instanceof Manx) 28 counter.count("EgyptianMau"); 29 if(pet instanceof Manx) 30 counter.count("Manx"); 31 if(pet instanceof Manx) 32 counter.count("Cymric"); 33 if(pet instanceof Rodent) 34 counter.count("Rodent"); 35 if(pet instanceof Rat) 36 counter.count("Rat"); 37 if(pet instanceof Mouse) 38 counter.count("Mouse"); 39 if(pet instanceof Hamster) 40 counter.count("Hamster"); 41 } 42 // Show the counts: 43 System.out.println(); 44 System.out.println(counter); 45 } 46 public static void main(String[] args) { 47 countPets(new ForNameCreator()); 48 } 49 } /* Output: 50 Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric 51 {Pug=3, Cat=9, Hamster=1, Cymric=7, Mouse=2, Mutt=3, Rodent=5, Pet=20, Manx=7, EgyptianMau=7, Dog=6, Rat=2} 52 *///:~
对instanceof有比较严格的限制,只可将其与命名类型进行比较,而不能与Class对象作比较。
使用类字面常量
使用类字面常量重新实现PetCount。
1 public class LiteralPetCreator extends PetCreator { 2 // No try block needed. 3 @SuppressWarnings("unchecked") 4 public static final List<Class<? extends Pet>> allTypes = 5 Collections.unmodifiableList(Arrays.asList( 6 Pet.class, Dog.class, Cat.class, Rodent.class, 7 Mutt.class, Pug.class, EgyptianMau.class, Manx.class, 8 Cymric.class, Rat.class, Mouse.class,Hamster.class)); 9 // Types for random creation: 10 private static final List<Class<? extends Pet>> types = 11 allTypes.subList(allTypes.indexOf(Mutt.class), 12 allTypes.size()); 13 public List<Class<? extends Pet>> types() { 14 return types; 15 } 16 public static void main(String[] args) { 17 System.out.println(types); 18 } 19 } /* Output: 20 [class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx, class typeinfo.pets.Cymric, class typeinfo.pets.Rat, class typeinfo.pets.Mouse, class typeinfo.pets.Hamster] 21 *///:~
动态的instanceof
Class.isInstance方法提供了一种动态地测试对象的途径。
1 public class PetCount3 { 2 static class PetCounter 3 extends LinkedHashMap<Class<? extends Pet>,Integer> { 4 public PetCounter() { 5 super(MapData.map(LiteralPetCreator.allTypes, 0)); 6 } 7 public void count(Pet pet) { 8 // Class.isInstance() eliminates instanceofs: 9 for(Map.Entry<Class<? extends Pet>,Integer> pair 10 : entrySet()) 11 if(pair.getKey().isInstance(pet)) 12 put(pair.getKey(), pair.getValue() + 1); 13 } 14 public String toString() { 15 StringBuilder result = new StringBuilder("{"); 16 for(Map.Entry<Class<? extends Pet>,Integer> pair 17 : entrySet()) { 18 result.append(pair.getKey().getSimpleName()); 19 result.append("="); 20 result.append(pair.getValue()); 21 result.append(", "); 22 } 23 result.delete(result.length()-2, result.length()); 24 result.append("}"); 25 return result.toString(); 26 } 27 } 28 public static void main(String[] args) { 29 PetCounter petCount = new PetCounter(); 30 for(Pet pet : Pets.createArray(20)) { 31 printnb(pet.getClass().getSimpleName() + " "); 32 petCount.count(pet); 33 } 34 print(); 35 print(petCount); 36 } 37 } /* Output: 38 Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric 39 {Pet=20, Dog=6, Cat=9, Rodent=5, Mutt=3, Pug=3, EgyptianMau=2, Manx=7, Cymric=5, Rat=2, Mouse=2, Hamster=1} 40 *///:~
可以看到,isInstance()方法使我们不再需要instanceof表达式。此外,这意味着如果要求添加新类型的Pet,只需简单地改变LiteralPetCreator数组即可,而无需改动程序其他部分。
instanceof与Class的等价性
1 class Base {} 2 class Derived extends Base {} 3 4 public class FamilyVsExactType { 5 static void test(Object x) { 6 System.out.println("Testing x of type " + x.getClass()); 7 System.out.println("x instanceof Base " + (x instanceof Base)); 8 System.out.println("x instanceof Derived " + (x instanceof Derived)); 9 System.out.println("Base.isInstance(x) " + Base.class.isInstance(x)); 10 System.out.println("Derived.isInstance(x) " + 11 Derived.class.isInstance(x)); 12 System.out.println("x.getClass() == Base.class " + 13 (x.getClass() == Base.class)); 14 System.out.println("x.getClass() == Derived.class " + 15 (x.getClass() == Derived.class)); 16 System.out.println("x.getClass().equals(Base.class)) " + 17 (x.getClass().equals(Base.class))); 18 System.out.println("x.getClass().equals(Derived.class)) " + 19 (x.getClass().equals(Derived.class))); 20 } 21 public static void main(String[] args) { 22 test(new Base()); 23 test(new Derived()); 24 } 25 } /* Output: 26 Testing x of type class typeinfo.Base 27 x instanceof Base true 28 x instanceof Derived false 29 Base.isInstance(x) true 30 Derived.isInstance(x) false 31 x.getClass() == Base.class true 32 x.getClass() == Derived.class false 33 x.getClass().equals(Base.class)) true 34 x.getClass().equals(Derived.class)) false 35 Testing x of type class typeinfo.Derived 36 x instanceof Base true 37 x instanceof Derived true 38 Base.isInstance(x) true 39 Derived.isInstance(x) true 40 x.getClass() == Base.class false 41 x.getClass() == Derived.class true 42 x.getClass().equals(Base.class)) false 43 x.getClass().equals(Derived.class)) true 44 *///:~
test()方法使用了两种形式的instanceof作为参数来执行类型检查。然后获取Class引用,并用==和equals()来检查Class对象是否相等。使人放心的是,instanceof和isInstance()生成的结果完全一样,equals()和==也一样。但是这两组测试得出的结论却不相同。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较实际的Class对象,就没有考虑继承--它或者是这个确切类型,或者不是。
反射:运行时类型信息
如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些有用的信息做一些有用的事。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。
初看起来这似乎不是一个限制,但是假设你获取了一个指向某个并不在你的程序空间中的对象的引用;事实上,在编译时你的程序根本没法获知这个对象所属的类。例如,假设你从磁盘文件,或者网络连接中获取了一串字节,并且你被
告知这串字节代表一个类。既然这个类在编译器为你的程序生成代码之后很久才会出现,那么怎样才能使用这样的类呢?
在传统的编程环境中不太可能出现这种情况。但是当我们置身于更大规模的编程世界中,在许多重要情况下就会发生上面的事情。首先就是“基于构建的编程”,在此种编程方式中,将使用基于快速应用开发(RAD)的应用构建工具,即
集成开发环境(IDE)来构项目。这是一种可视化编程方法,可以通过将代表不同组件的图标拖拽到表单中来创建程序。然后在编程时通过设置构建的属性值来配置它们。这种设计时的配置,要求构建都是可实例化的,并且要暴露其部分信息,
以允许程序员读取和修改构建的属性。此外处理图形化界面(GUI)事件的构建还必须暴露相关方法的信息,以便IDE能够帮助程序员覆盖这些处理事件的方法。反射提供了一种机制,用来检查可用的方法,并返回方法名。
人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力。这被称为远程方法调用(RMI),它允许一个Java程序将对象分布到多态机器上。需要这种分布能力是有许多原因的。例如,
你可能正在进行一项需进行大量计算的任务,为了提高运算速度,想将计算划分为许多小的计算单元,分布到空闲的机器上运行。
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Construcotr创建新的对象,
用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对相关联的方法。另外,还可以调用getFields()、getMethod()和getConstructors()等很便利的方法,以返回表示字段,方法以及构造器的对象的数组。
这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
重要的是,要认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只是简单的检查这个对象,看它属于哪个特定的类。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件对于
JVM来说必须是可获取的,要么在本地机器上,要么在远程网络上。所以RTTI和反射之间真正的区别只在于:对RTTI来说,编译器在编译时打开和检查.class文件。(换句话说,我们可以用普通方式调用对象的所有方法。)而对于反射机制来说,
.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
类方法提取器
通常你不需要直接使用反射工具,但是它们在你需要创建更加动态的代码时会很有用。反射在java中是用来支持其他特性的,例如对象序列化和JavaBean。但是,如果能动态地提取某个类的信息有的时候还是很有用的,请考虑类方法提取器。反射机制提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具。下面是其工作方式
1 public class ShowMethods { 2 private static String usage = 3 "usage:\n" + 4 "ShowMethods qualified.class.name\n" + 5 "To show all methods in class or:\n" + 6 "ShowMethods qualified.class.name word\n" + 7 "To search for methods involving 'word'"; 8 private static Pattern p = Pattern.compile("\\w+\\."); 9 public static void main(String[] args) { 10 if(args.length < 1) { 11 System.out.println(usage); 12 System.exit(0); 13 } 14 int lines = 0; 15 try { 16 Class<?> c = Class.forName(args[0]); 17 Method[] methods = c.getMethods(); 18 Constructor[] ctors = c.getConstructors(); 19 if(args.length == 1) { 20 for(Method method : methods) 21 System.out.println( 22 p.matcher(method.toString()).replaceAll("")); 23 for(Constructor ctor : ctors) 24 System.out.println(p.matcher(ctor.toString()).replaceAll("")); 25 lines = methods.length + ctors.length; 26 } else { 27 for(Method method : methods) 28 if(method.toString().indexOf(args[1]) != -1) { 29 System.out.println( 30 p.matcher(method.toString()).replaceAll("")); 31 lines++; 32 } 33 for(Constructor ctor : ctors) 34 if(ctor.toString().indexOf(args[1]) != -1) { 35 System.out.println(p.matcher( 36 ctor.toString()).replaceAll("")); 37 lines++; 38 } 39 } 40 } catch(ClassNotFoundException e) { 41 System.out.println("No such class: " + e); 42 } 43 } 44 } /* Output: 45 public static void main(String[]) 46 public native int hashCode() 47 public final native Class getClass() 48 public final void wait(long,int) throws InterruptedException 49 public final void wait() throws InterruptedException 50 public final native void wait(long) throws InterruptedException 51 public boolean equals(Object) 52 public String toString() 53 public final native void notify() 54 public final native void notifyAll() 55 public ShowMethods() 56 *///:~
动态代理
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替实际对象的对象。这些操作通常涉及与实际对象的通信,因此代理通常充当中间人的角色。
1 interface Interface { 2 void doSomething(); 3 void somethingElse(String arg); 4 } 5 6 class RealObject implements Interface { 7 public void doSomething() { System.out.println("doSomething"); } 8 public void somethingElse(String arg) { 9 System.out.println("somethingElse " + arg); 10 } 11 } 12 13 class SimpleProxy implements Interface { 14 private Interface proxied; 15 public SimpleProxy(Interface proxied) { 16 this.proxied = proxied; 17 } 18 public void doSomething() { 19 System.out.println("SimpleProxy doSomething"); 20 proxied.doSomething(); 21 } 22 public void somethingElse(String arg) { 23 System.out.println("SimpleProxy somethingElse " + arg); 24 proxied.somethingElse(arg); 25 } 26 } 27 28 class SimpleProxyDemo { 29 public static void consumer(Interface iface) { 30 iface.doSomething(); 31 iface.somethingElse("bonobo"); 32 } 33 public static void main(String[] args) { 34 consumer(new RealObject()); 35 consumer(new SimpleProxy(new RealObject())); 36 } 37 } /* Output: 38 doSomething 39 somethingElse bonobo 40 SimpleProxy doSomething 41 doSomething 42 SimpleProxy somethingElse bonobo 43 somethingElse bonobo 44 *///:~
Java动态代理比代理思想更进一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理商所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
1 class DynamicProxyHandler implements InvocationHandler { 2 private Object proxied; 3 public DynamicProxyHandler(Object proxied) { 4 this.proxied = proxied; 5 } 6 public Object 7 invoke(Object proxy, Method method, Object[] args) 8 throws Throwable { 9 System.out.println("**** proxy: " + proxy.getClass() + 10 ", method: " + method + ", args: " + args); 11 if(args != null) 12 for(Object arg : args) 13 System.out.println(" " + arg); 14 return method.invoke(proxied, args); 15 } 16 } 17 18 class SimpleDynamicProxy { 19 public static void consumer(Interface iface) { 20 iface.doSomething(); 21 iface.somethingElse("bonobo"); 22 } 23 public static void main(String[] args) { 24 RealObject real = new RealObject(); 25 consumer(real); 26 // Insert a proxy and call again: 27 Interface proxy = (Interface)Proxy.newProxyInstance( 28 Interface.class.getClassLoader(), 29 new Class[]{ Interface.class }, 30 new DynamicProxyHandler(real)); 31 consumer(proxy); 32 } 33 } /* Output: (95% match) 34 doSomething 35 somethingElse bonobo 36 **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null 37 doSomething 38 **** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816 39 bonobo 40 somethingElse bonobo 41 *///:~
空对象
当你使用内置的null表示缺少对象时,在每次使用引用时都必须测试其是否为null,这显得枯燥,而且势必产生相当乏味的代码。问题在于null除了在你试图用它执行任何操作来产生NullPointerException之外,它自己没有其他任何行为。有时引入空对象的思想将会很有用,它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费精力去检查null。
尽管想象一种可以自动为我们创建空对象的编程语言显得很有趣,但是实际上,到处使用空对象并没有任何意义,有时检查null就可以了,有时你可以合理地假设你根本不会遇到null,有时甚至通过NullPointerException来探测异常也可以接受的,空对象最有用之处在于它更靠近数据,因为对象表示的问题空间内的实体。有一个简单的例子,许多系统都有一个Person类,而在代码中,有很多情况是你没有一个实际的人。因此,通常你会用一个null引用并测试它。与此不同的是,我们可以使用空对象。但是即使空对象可以相应“实际”对象可以相应的所有消息,你仍需要某种方式去测试其是否为空。要达到此目的,最简单的方式是创建一个标记接口:
1 public interface Null {} ///:~
这使得instanceof可以探测空对象,更重要的是,这并不要求你所有的类中添加isNull()方法。
1 class Person { 2 public final String first; 3 public final String last; 4 public final String address; 5 // etc. 6 public Person(String first, String last, String address){ 7 this.first = first; 8 this.last = last; 9 this.address = address; 10 } 11 public String toString() { 12 return "Person: " + first + " " + last + " " + address; 13 } 14 public static class NullPerson 15 extends Person implements Null { 16 private NullPerson() { super("None", "None", "None"); } 17 public String toString() { return "NullPerson"; } 18 } 19 public static final Person NULL = new NullPerson(); 20 } ///:~
现在假设你回到了互联网刚出现的雄心万丈的年代,并且你已经因你惊人的理念而获得了一大笔风险投资。你现在要招兵买马了,但是在虚位以待时,你可以将person空对象放在每个Position上
1 class Position { 2 private String title; 3 private Person person; 4 public Position(String jobTitle, Person employee) { 5 title = jobTitle; 6 person = employee; 7 if(person == null) 8 person = Person.NULL; 9 } 10 public Position(String jobTitle) { 11 title = jobTitle; 12 person = Person.NULL; 13 } 14 public String getTitle() { return title; } 15 public void setTitle(String newTitle) { 16 title = newTitle; 17 } 18 public Person getPerson() { return person; } 19 public void setPerson(Person newPerson) { 20 person = newPerson; 21 if(person == null) 22 person = Person.NULL; 23 } 24 public String toString() { 25 return "Position: " + title + " " + person; 26 } 27 } ///:~
如果你用接口取代具体类,那么就可以使用DynamicProxy来自动地创建空对象。假设我们有一个Robot接口,它定义了一个名字、一个模型和一个描述Robot行为能力的List<Operation>.Operation包含一个描述和一个命令。
1 public interface Operation { 2 String description(); 3 void command(); 4 } ///:~ 5 public interface Robot { 6 String name(); 7 String model(); 8 List<Operation> operations(); 9 class Test { 10 public static void test(Robot r) { 11 if(r instanceof Null) 12 System.out.println("[Null Robot]"); 13 System.out.println("Robot name: " + r.name()); 14 System.out.println("Robot model: " + r.model()); 15 for(Operation operation : r.operations()) { 16 System.out.println(operation.description()); 17 operation.command(); 18 } 19 } 20 } 21 } ///:~ 22 public class SnowRemovalRobot implements Robot { 23 private String name; 24 public SnowRemovalRobot(String name) {this.name = name;} 25 public String name() { return name; } 26 public String model() { return "SnowBot Series 11"; } 27 public List<Operation> operations() { 28 return Arrays.asList( 29 new Operation() { 30 public String description() { 31 return name + " can shovel snow"; 32 } 33 public void command() { 34 System.out.println(name + " shoveling snow"); 35 } 36 }, 37 new Operation() { 38 public String description() { 39 return name + " can chip ice"; 40 } 41 public void command() { 42 System.out.println(name + " chipping ice"); 43 } 44 }, 45 new Operation() { 46 public String description() { 47 return name + " can clear the roof"; 48 } 49 public void command() { 50 System.out.println(name + " clearing roof"); 51 } 52 } 53 ); 54 } 55 public static void main(String[] args) { 56 Robot.Test.test(new SnowRemovalRobot("Slusher")); 57 } 58 } /* Output: 59 Robot name: Slusher 60 Robot model: SnowBot Series 11 61 Slusher can shovel snow 62 Slusher shoveling snow 63 Slusher can chip ice 64 Slusher chipping ice 65 Slusher can clear the roof 66 Slusher clearing roof 67 *///:~
假设存在许多不同类型的Robot,我们想对每一种Robot类型都创建一个空对象,去执行某些特殊操作--在本例中,即提供空对象所代表的Robot确切类型的信息。这些信息是通过动态代理捕获的。
1 class NullRobotProxyHandler implements InvocationHandler { 2 private String nullName; 3 private Robot proxied = new NRobot(); 4 NullRobotProxyHandler(Class<? extends Robot> type) { 5 nullName = type.getSimpleName() + " NullRobot"; 6 } 7 private class NRobot implements Null, Robot { 8 public String name() { return nullName; } 9 public String model() { return nullName; } 10 public List<Operation> operations() { 11 return Collections.emptyList(); 12 } 13 } 14 public Object 15 invoke(Object proxy, Method method, Object[] args) 16 throws Throwable { 17 return method.invoke(proxied, args); 18 } 19 } 20 21 public class NullRobot { 22 public static Robot 23 newNullRobot(Class<? extends Robot> type) { 24 return (Robot)Proxy.newProxyInstance( 25 NullRobot.class.getClassLoader(), 26 new Class[]{ Null.class, Robot.class }, 27 new NullRobotProxyHandler(type)); 28 } 29 public static void main(String[] args) { 30 Robot[] bots = { 31 new SnowRemovalRobot("SnowBee"), 32 newNullRobot(SnowRemovalRobot.class) 33 }; 34 for(Robot bot : bots) 35 Robot.Test.test(bot); 36 } 37 } /* Output: 38 Robot name: SnowBee 39 Robot model: SnowBot Series 11 40 SnowBee can shovel snow 41 SnowBee shoveling snow 42 SnowBee can chip ice 43 SnowBee chipping ice 44 SnowBee can clear the roof 45 SnowBee clearing roof 46 [Null Robot] 47 Robot name: SnowRemovalRobot NullRobot 48 Robot model: SnowRemovalRobot NullRobot 49 *///:~
无论何时,如果你需要一个空Robot对象,只需要调用newNullRobot(),并传递需要代理的Robot的类型。代理会满足Robot和Null接口的需求,并提供它所代理的类型的确切名字。
接口与类型信息
interface关键字的一个重要目标就是允许程序员隔离构件、进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去--接口并非是对解耦的一种无懈可击的保障。
1 public interface A { 2 void f(); 3 } ///:~
然后实现这个接口,你可以看到其代码是如何围绕着实际的实现类型潜行的
1 class B implements A { 2 public void f() {} 3 public void g() {} 4 } 5 6 public class InterfaceViolation { 7 public static void main(String[] args) { 8 A a = new B(); 9 a.f(); 10 // a.g(); // Compile error 11 System.out.println(a.getClass().getName()); 12 if(a instanceof B) { 13 B b = (B)a; 14 b.g(); 15 } 16 } 17 } /* Output: 18 B 19 *///:~
通过使用RTTI,我们发现a是被当做B实现的。通过将其转型为B,我们可以调用不在A中的方法。
这是完全合法和可接受的,但是你也许并不像让客户端程序员这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过你的期望。也就是说,你可能认为interface关键字正在保护你,但是它并没有。
一种解决方案是直接声明,如果程序员决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下可能都是合理地,但“可能”还不够,你也许希望应用一些更严苛的控制。
最简单的方式是使用包访问权限
1 class C implements A { 2 public void f() { 3 System.out.println("public C.f()"); } 4 public void g() { System.out.println("public C.g()"); } 5 void u() { System.out.println("package C.u()"); } 6 protected void v() { System.out.println("protected C.v()"); } 7 private void w() { System.out.println("private C.w()"); } 8 } 9 10 public class HiddenC { 11 public static A makeA() { return new C(); } 12 } ///:~
如果你试图将其向下转型为C,则将被禁止。
1 public class HiddenImplementation { 2 public static void main(String[] args) throws Exception { 3 A a = HiddenC.makeA(); 4 a.f(); 5 System.out.println(a.getClass().getName()); 6 // Compile error: cannot find symbol 'C': 7 /* if(a instanceof C) { 8 C c = (C)a; 9 c.g(); 10 } */ 11 // Oops! Reflection still allows us to call g(): 12 callHiddenMethod(a, "g"); 13 // And even methods that are less accessible! 14 callHiddenMethod(a, "u"); 15 callHiddenMethod(a, "v"); 16 callHiddenMethod(a, "w"); 17 } 18 static void callHiddenMethod(Object a, String methodName) 19 throws Exception { 20 Method g = a.getClass().getDeclaredMethod(methodName); 21 g.setAccessible(true); 22 g.invoke(a); 23 } 24 } /* Output: 25 public C.f() 26 typeinfo.packageaccess.C 27 public C.g() 28 package C.u() 29 protected C.v() 30 private C.w() 31 *///:~
正如你看到的,通过使用反射,仍旧可以到达并调用所有方法,甚至是private方法!如果知道方法名,你就可以在Method对象上调用setAccessible(true),就像在callHiddenMethod()中看到的那样。
我们看看私有内部类是什么情况?
1 class InnerA { 2 private static class C implements A { 3 public void f() { 4 System.out.println("public C.f()"); } 5 public void g() { System.out.println("public C.g()"); } 6 void u() { System.out.println("package C.u()"); } 7 protected void v() { System.out.println("protected C.v()"); } 8 private void w() { System.out.println("private C.w()"); } 9 } 10 public static A makeA() { return new C(); } 11 } 12 13 public class InnerImplementation { 14 public static void main(String[] args) throws Exception { 15 A a = InnerA.makeA(); 16 a.f(); 17 System.out.println(a.getClass().getName()); 18 // Reflection still gets into the private class: 19 HiddenImplementation.callHiddenMethod(a, "g"); 20 HiddenImplementation.callHiddenMethod(a, "u"); 21 HiddenImplementation.callHiddenMethod(a, "v"); 22 HiddenImplementation.callHiddenMethod(a, "w"); 23 } 24 } /* Output: 25 public C.f() 26 InnerA$C 27 public C.g() 28 package C.u() 29 protected C.v() 30 private C.w() 31 *///:~
看来,反射一样免疫。
再看看匿名类
1 class AnonymousA { 2 public static A makeA() { 3 return new A() { 4 public void f() { 5 System.out.println("public C.f()"); } 6 public void g() { System.out.println("public C.g()"); } 7 void u() { System.out.println("package C.u()"); } 8 protected void v() { System.out.println("protected C.v()"); } 9 private void w() { System.out.println("private C.w()"); } 10 }; 11 } 12 } 13 14 public class AnonymousImplementation { 15 public static void main(String[] args) throws Exception { 16 A a = AnonymousA.makeA(); 17 a.f(); 18 System.out.println(a.getClass().getName()); 19 // Reflection still gets into the anonymous class: 20 HiddenImplementation.callHiddenMethod(a, "g"); 21 HiddenImplementation.callHiddenMethod(a, "u"); 22 HiddenImplementation.callHiddenMethod(a, "v"); 23 HiddenImplementation.callHiddenMethod(a, "w"); 24 } 25 } /* Output: 26 public C.f() 27 AnonymousA$1 28 public C.g() 29 package C.u() 30 protected C.v() 31 private C.w() 32 *///:~
一样免疫。
看起来没有任何方式可以阻止反射到达并调用那些非公共访问权限的方法。对于域来说,的确如此,即便是private域。
1 class WithPrivateFinalField { 2 private int i = 1; 3 private final String s = "I'm totally safe"; 4 private String s2 = "Am I safe?"; 5 public String toString() { 6 return "i = " + i + ", " + s + ", " + s2; 7 } 8 } 9 10 public class ModifyingPrivateFields { 11 public static void main(String[] args) throws Exception { 12 WithPrivateFinalField pf = new WithPrivateFinalField(); 13 System.out.println(pf); 14 Field f = pf.getClass().getDeclaredField("i"); 15 f.setAccessible(true); 16 System.out.println("f.getInt(pf): " + f.getInt(pf)); 17 f.setInt(pf, 47); 18 System.out.println(pf); 19 f = pf.getClass().getDeclaredField("s"); 20 f.setAccessible(true); 21 System.out.println("f.get(pf): " + f.get(pf)); 22 f.set(pf, "No, you're not!"); 23 System.out.println(pf); 24 f = pf.getClass().getDeclaredField("s2"); 25 f.setAccessible(true); 26 System.out.println("f.get(pf): " + f.get(pf)); 27 f.set(pf, "No, you're not!"); 28 System.out.println(pf); 29 } 30 } /* Output: 31 i = 1, I'm totally safe, Am I safe? 32 f.getInt(pf): 1 33 i = 47, I'm totally safe, Am I safe? 34 f.get(pf): I'm totally safe 35 i = 47, I'm totally safe, Am I safe? 36 f.get(pf): Am I safe? 37 i = 47, I'm totally safe, No, you're not! 38 *///:~