11.2 RTTI语法

Java用Class对象实现自己的RTTI功能——即便我们要做的只是象转换那样的一些工作。Class类也提供了其他大量方式,以方便我们使用RTTI。

首先必须获得指向适当Class对象的的一个引用。就象前例演示的那样,一个办法是用一个字符串以及Class.forName()方法。这是非常方便的,因为不需要那种类型的一个对象来获取Class引用。然而,对于自己感兴趣的类型,如果已有了它的一个对象,那么为了取得Class引用,可调用属于Object根类一部分的一个方法:getClass()。它的作用是返回一个特定的Class引用,用来表示对象的实际类型。Class提供了几个有趣且较为有用的方法,从下例即可看出:

  1. //: ToyTest.java
  2. // Testing class Class
  3. interface HasBatteries {}
  4. interface Waterproof {}
  5. interface ShootsThings {}
  6. class Toy {
  7. // Comment out the following default
  8. // constructor to see
  9. // NoSuchMethodError from (*1*)
  10. Toy() {}
  11. Toy(int i) {}
  12. }
  13. class FancyToy extends Toy
  14. implements HasBatteries,
  15. Waterproof, ShootsThings {
  16. FancyToy() { super(1); }
  17. }
  18. public class ToyTest {
  19. public static void main(String[] args) {
  20. Class c = null;
  21. try {
  22. c = Class.forName("FancyToy");
  23. } catch(ClassNotFoundException e) {}
  24. printInfo(c);
  25. Class[] faces = c.getInterfaces();
  26. for(int i = 0; i < faces.length; i++)
  27. printInfo(faces[i]);
  28. Class cy = c.getSuperclass();
  29. Object o = null;
  30. try {
  31. // Requires default constructor:
  32. o = cy.newInstance(); // (*1*)
  33. } catch(InstantiationException e) {}
  34. catch(IllegalAccessException e) {}
  35. printInfo(o.getClass());
  36. }
  37. static void printInfo(Class cc) {
  38. System.out.println(
  39. "Class name: " + cc.getName() +
  40. " is interface? [" +
  41. cc.isInterface() + "]");
  42. }
  43. } ///:~

从中可以看出,class FancyToy相当复杂,因为它从Toy中继承,并实现了HasBatteriesWaterproof以及ShootsThings的接口。在main()中创建了一个Class引用,并用位于相应try块内的forName()初始化成FancyToy

Class.getInterfaces方法会返回Class对象的一个数组,用于表示包含在Class对象内的接口。

若有一个Class对象,也可以用getSuperclass()查询该对象的直接基类是什么。当然,这种做会返回一个Class引用,可用它作进一步的查询。这意味着在运行期的时候,完全有机会调查到对象的完整层次结构。

若从表面看,ClassnewInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别的。利用newInstance(),我们可在没有现成对象供“克隆”的情况下新建一个对象。就象上面的程序演示的那样,当时没有Toy对象,只有cy——即yClass对象的一个引用。利用它可以实现“虚拟构造器”。换言之,我们表达:“尽管我不知道你的准确类型是什么,但请你无论如何都正确地创建自己。”在上述例子中,cy只是一个Class引用,编译期间并不知道进一步的类型信息。一旦新建了一个实例后,可以得到Object引用。但那个引用指向一个Toy对象。当然,如果要将除Object能够接收的其他任何消息发出去,首先必须进行一些调查研究,再进行转换。除此以外,用newInstance()创建的类必须有一个默认构造器。没有办法用newInstance()创建拥有非默认构造器的对象,所以在Java 1.0中可能存在一些限制。然而,Java 1.1的“反射”API(下一节讨论)却允许我们动态地使用类里的任何构造器。

程序中的最后一个方法是printInfo(),它取得一个Class引用,通过getName()获得它的名字,并用interface()调查它是不是一个接口。

该程序的输出如下:

  1. Class name: FancyToy is interface? [false]
  2. Class name: HasBatteries is interface? [true]
  3. Class name: Waterproof is interface? [true]
  4. Class name: ShootsThings is interface? [true]
  5. Class name: Toy is interface? [false]

所以利用Class对象,我们几乎能将一个对象的祖宗十八代都调查出来。