第十章 类型检查
运行时类型识别(run-time type identification,RTTI)的概念初看起来非常简单;当只有一个指向对象基类的引用时,RTTI机制可以让你找出这个对象的确切类型。
运行时识别对象和类的信息。只要有两种方式:1.“传统的”RTTI,它假定我们在编译时和运行时已经知道了所有的类型;2.“反射机制”,它运行我们在运行时获得类的信息。
RTTI
import com.bruceeckel.simpletest.*;
class Shape { void draw() { System.out.println(this + ".draw()"); } }
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 { private static Test monitor = new Test(); public static void main(String[] args) { // Array of Object, not Shape:
Object[] shapeList = { new Circle(), new Square(), new Triangle() }; for(int i = 0; i < shapeList.length; i++) ((Shape)shapeList[i]).draw(); // Must cast
monitor.expect(new String[] { "Circle.draw()", "Square.draw()", "Triangle.draw()" }); } } ///:~
|
在这个例子中,RTTI类型转换并不彻底,Object被转型为Shape,而不是转型为Circle、Square或者Triangle。这是因为目前我们只知道这个数组保存的都是Shape。在编译时,这只能由你自己设定的规则来强制确保这一点;而在运行时,有类型转换操作来确保这一点。接下来就是多态机制的事情了,Shape对象实际执行什么样的代码,是由引用所执行的具体对象Circle、Square后者Triangle而决定的。
Class对象
在运行时,当我们像生成这个类的对象时,运行这个程序的Java虚拟机(JVM)首先检查这个类的Class对象是否已经加载。如果尚未加载,JVM就会根据类名查找.Class文件,并将其载入。所以Java程序并不是一开始就被完全加载的,这一点域传语言都不同。
import com.bruceeckel.simpletest.*;
class Candy { static { System.out.println("Loading Candy"); } }
class Gum { static { System.out.println("Loading Gum"); } }
class Cookie { static { System.out.println("Loading Cookie"); } }
public class SweetShop { private static Test monitor = new Test(); public static void main(String[] args) { System.out.println("inside main"); new Candy(); System.out.println("After creating Candy"); try { Class.forName("Gum"); } catch(ClassNotFoundException e) { System.out.println("Couldn't find Gum"); } System.out.println("After Class.forName(\"Gum\")"); new Cookie(); System.out.println("After creating Cookie"); monitor.expect(new String[] { "inside main", "Loading Candy", "After creating Candy", "Loading Gum", "After Class.forName(\"Gum\")", "Loading Cookie", "After creating Cookie" }); } }
|
Class对象仅在需要的时候才被加载。
Class.forName("Gum");
forName()是取得Class对象的引用的一种方法。对forName()的调用是为了它产生的“副作用”;如果类Gum还没有被加载就加载它
类字面常量
上面的语句等价与Gum.class,这是一种“类字面常量”。这样做不仅简单,而且更安全,因为它在编译时就会收到检查。并且它无需方法调用,所以也更高效。类字面产量不仅可以应用域普通类,也可以用应用于接口、数组及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对象的基本数据类型的Class对象,(例如int.class == Integer.TYPE),但建议使用".class"形式,保持与普通类的一致性。
类型转换前先做检查
RTTI形式包括
1)传统的类型转换,如“(Shape)”,由RTTI确保类型转换的准确性,如果转型了一个错误的类型转换,就会抛出一个ClassCastException异常。
2)代表对象的Class对象。通过查询Class对象可以获取运行时所需的信息。
3)instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
if( x instanceof Dog) ((Dog)x).bark();使用instanceof非常重要,否则会得到一个ClassCastException异常。
package c10; import com.bruceeckel.simpletest.*; import java.util.*;
public class PetCount { private static Test monitor = new Test(); private static Random rand = new Random(); static String[] typenames = { "Pet", "Dog", "Pug", "Cat", "Rodent", "Gerbil", "Hamster", }; // Exceptions thrown to console:
public static void main(String[] args) { Object[] pets = new Object[15]; try { Class[] petTypes = { Class.forName("c10.Dog"), Class.forName("c10.Pug"), Class.forName("c10.Cat"), Class.forName("c10.Rodent"), Class.forName("c10.Gerbil"), Class.forName("c10.Hamster"), }; for(int i = 0; i < pets.length; i++) pets[i] = petTypes[rand.nextInt(petTypes.length)] .newInstance(); } catch(InstantiationException e) { System.out.println("Cannot instantiate"); System.exit(1); } catch(IllegalAccessException e) { System.out.println("Cannot access"); System.exit(1); } catch(ClassNotFoundException e) { System.out.println("Cannot find class"); System.exit(1); } AssociativeArray map = new AssociativeArray(typenames.length); for(int i = 0; i < typenames.length; i++) map.put(typenames[i], new Counter()); for(int i = 0; i < pets.length; i++) { Object o = pets[i]; if(o instanceof Pet) ((Counter)map.get("Pet")).i++; if(o instanceof Dog) ((Counter)map.get("Dog")).i++; if(o instanceof Pug) ((Counter)map.get("Pug")).i++; if(o instanceof Cat) ((Counter)map.get("Cat")).i++; if(o instanceof Rodent) ((Counter)map.get("Rodent")).i++; if(o instanceof Gerbil) ((Counter)map.get("Gerbil")).i++; if(o instanceof Hamster) ((Counter)map.get("Hamster")).i++; } // List each individual pet:
for(int i = 0; i < pets.length; i++) System.out.println(pets[i].getClass()); // Show the counts:
System.out.println(map); monitor.expect(new Object[] { new TestExpression("%% class c10\\."+ "(Dog|Pug|Cat|Rodent|Gerbil|Hamster)", pets.length), new TestExpression( "%% (Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster)" + " : \\d+", typenames.length) }); } }
|
Class.newInstance()使用所选的Class对象生成该类实例。
IllegalAccessException表示违背Java的安全机制。
对instanceof有比较严格的限制:只有将器与命名类型进行比较,而不能与Class对象做比较。
动态的instanceof
Class.inInstance方法提供了一种动态地调用instanceof约瑟夫的途径。于是所有那些单调的instanceof语句都可以从PetCount的例子一处。
package c10; import com.bruceeckel.simpletest.*; import java.util.*;
public class PetCount3 { private static Test monitor = new Test(); private static Random rand = new Random(); public static void main(String[] args) { Object[] pets = new Object[15]; Class[] petTypes = { // Class literals:
Pet.class, Dog.class, Pug.class, Cat.class, Rodent.class, Gerbil.class, Hamster.class, }; try { for(int i = 0; i < pets.length; i++) { // Offset by one to eliminate Pet.class:
int rnd = 1 + rand.nextInt(petTypes.length - 1); pets[i] = petTypes[rnd].newInstance(); } } catch(InstantiationException e) { System.out.println("Cannot instantiate"); System.exit(1); } catch(IllegalAccessException e) { System.out.println("Cannot access"); System.exit(1); } AssociativeArray map = new AssociativeArray(petTypes.length); for(int i = 0; i < petTypes.length; i++) map.put(petTypes[i].toString(), new Counter()); for(int i = 0; i < pets.length; i++) { Object o = pets[i]; // Using Class.isInstance() to eliminate
// individual instanceof expressions:
for(int j = 0; j < petTypes.length; ++j) if(petTypes[j].isInstance(o)) ((Counter)map.get(petTypes[j].toString())).i++; } // List each individual pet:
for(int i = 0; i < pets.length; i++) System.out.println(pets[i].getClass()); // Show the counts:
System.out.println(map); monitor.expect(new Object[] { new TestExpression("%% class c10\\." + "(Dog|Pug|Cat|Rodent|Gerbil|Hamster)", pets.length), new TestExpression("%% class c10\\." + "(Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster) : \\d+", petTypes.length) }); } }
|
等价性:instanceof与Class
package c10; import com.bruceeckel.simpletest.*;
class Base {} class Derived extends Base {}
public class FamilyVsExactType { private static Test monitor = new Test(); 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()); monitor.expect(new String[] { "Testing x of type class c10.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 c10.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" }); } }
|
instanceof保持了类型的概念,它值的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较实际的Class对象,就没有考虑继承--它或者是这个确切类型,或者不是。
RTTI语法
import com.bruceeckel.simpletest.*;
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 { private static Test monitor = new Test(); static void printInfo(Class cc) { System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]"); } public static void main(String[] args) { Class c = null; try { c = Class.forName("FancyToy"); } catch(ClassNotFoundException e) { System.out.println("Can't find FancyToy"); System.exit(1); } printInfo(c); Class[] faces = c.getInterfaces(); for(int i = 0; i < faces.length; i++) printInfo(faces[i]); Class cy = c.getSuperclass(); Object o = null; try { // Requires default constructor: o = cy.newInstance(); // (*1*) } catch(InstantiationException e) { System.out.println("Cannot instantiate"); System.exit(1); } catch(IllegalAccessException e) { System.out.println("Cannot access"); System.exit(1); } printInfo(o.getClass()); monitor.expect(new String[] { "Class name: FancyToy is interface? [false]", "Class name: HasBatteries is interface? [true]", "Class name: Waterproof is interface? [true]", "Class name: Shoots is interface? [true]", "Class name: Toy is interface? [false]" }); } }
|
Class.getInterfaces()方法返回Class对象的数组,这些对象代表的是某个Class对象所包含的接口
getSuperclass()获取它的直接基类
使用newInstance()而创建的类必须有一个缺省构造器
Iprintnfo(),它以一个Class引用为参数,通过getName()获取器名字,并通过isInterface()查看它是否是一个接口
反射:运行时的类信息
这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。获取了一个指向某个并不在你的程序空间中的对象的引用;事实上,在编译时你的程序根本没法获知这个对象所属的类。
人们想要在运行时获取类的信息的其中一个动机是:希望提供在跨网络的远程平台上创建和运行对象的能力。称为远程方法调用(RMI),它允许一个Java程序将对象发布在多台机器上。
Class类支持反射概念,Java附带的库Java.lang.reflect包括了Field、Method以及Constructor类,可以使用Constructor创建新的对象,get()和set()方法读取修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法...
RTTI与反射之间的真正区别只在于,对RTTI来说,编译器在编译时开始检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时开始检查.class文件
类方法提取器
阅读实现了类定义的源代码或是其JDK文档,只能找到在这个类定义中被定义或被覆盖的方法。但对你来说,可能有数十个更有用的方法都是继承基类的。要指出这些方法可能会很乏味且费时。幸运的是,反射机制提供了一种方法。
// Using reflection to show all the methods of a class, // even if the methods are defined in the base class.
import java.lang.reflect.*; import java.util.regex.*;
public class ShowMethods { private static final String usage = "usage: \n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class c = Class.forName(args[0]); Method[] m = c.getMethods(); Constructor[] ctor = c.getConstructors(); if(args.length == 1) { for(int i = 0; i < m.length; i++) System.out.println( p.matcher(m[i].toString()).replaceAll("")); for(int i = 0; i < ctor.length; i++) System.out.println( p.matcher(ctor[i].toString()).replaceAll("")); lines = m.length + ctor.length; } else { for(int i = 0; i < m.length; i++) if(m[i].toString().indexOf(args[1]) != -1) { System.out.println( p.matcher(m[i].toString()).replaceAll("")); lines++; } for(int i = 0; i < ctor.length; i++) if(ctor[i].toString().indexOf(args[1]) != -1) { System.out.println(p.matcher( ctor[i].toString()).replaceAll("")); lines++; } } } catch(ClassNotFoundException e) { System.out.println("No such class: " + e); } } } ///:~
|
Class的getMethods()和getConstuctor()方法分别发挥Method对象的数组和Constructor对象的数组。
Class.forName()生成的对象在编译时是不可知的,因此所有的方法特镇签名信息都是在执行时被提取出来的。反射机制提供了足够的支持,使得能够创建一个在编译时完全位置的对象并调用此对象的方法。
阅读(609) | 评论(0) | 转发(0) |