RTTI的使用场景:
例如,类的关系如上图所示,我们可以像下面代码一样将它们放进一个Vector中:
public static void main(String[] args) {Vector s = new Vector();s.addElement(new Circle());s.addElement(new Square());s.addElement(new Triangle());Enumeration e = s.elements();while(e.hasMoreElements())((Shape)e.nextElement()).draw();
}
因为Vector中存放的都是Object句柄,所以取出后进行下溯造型为Shape.。这是 RTTI 最基本的形式,因为在 Java 中,所有造型都会在运行期间得到检查,以确保其正确性。那正是RTTI 的意义所在:在运行期,对象的类型会得到鉴定。
但我们也只能下溯到Shape, 并不能得出它到底是哪一种图形。如果我们需要找出所有的圆并填充蓝色,就要使用RTTI技术用它查找某个Shape句柄到底是什么类型。
Class对象:
理解RTTI 的工作原理要先了解Class对象。 Class 对象是用来创建属于某个类的全部“常规”或“普通”对象。 每个类都有一个对应的 Class对象。换言之,每次写一个新类时,同时也会创建一个Class 对象(更恰当地说,是保存在一个完全同名的.class 文件中)。在运行期,一旦我们想生成哪个类的一个对象,Java 虚拟机(JVM)首先就会检查那个类型的Class对象是否已经载入。若未载入,JVM就会查找同名的.class 文件并将其载入。所以Java 程序启动时并不是完全载入的,这一点与许多传统语言都不同。 一旦那个类型的Class 对象进入内存,就用它创建那一类型的所有对象。
为获得 Class的一个句柄,一个办法是使用forName()。它的作用是取得包含了目标类文本名字的一个 String(注意拼写和大小写)。最后返回的是一个Class 句柄。 第二种方式是使用“类标记”。举例说明:
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 {public static void main(String[] args) {new Candy();System.out.println("After creating Candy");Class.forName("Gum"); //使用forName()System.out.println( "After Class.forName(\"Gum\")");Cookie.class; //使用类标记System.out.println("After Cookie.class");}
}
对每个类来说(Candy,Gum 和Cookie),都有一个 static从句,用于在类首次载入时执行。相应的信息会打印出来,告诉我们载入是什么时候进行的。最后打印结果为:
|
更推荐使用类标记,因为它会在编译期得到检查并且因为取消了对方法的调用,效率更高。
类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据 类型的封装器类,它还存在一个名为TYPE 的标准字段。TYPE 字段的作用是为相关的基本数据类型产生 Class 对象的一个句柄,如下所示:
造型前的检查:
- 静态检查:instanceof关键字
- 动态检查:isInstance()方法
关键字instanceof告诉我们对象是不是一个特定类型的实例。它会返回一个布尔值:
if(x instanceof Dog) ((Dog)x).bark();
在Java 1.0 中,对 instanceof 有一个比较小的限制:只可将其与一个已命名的类型比较,不能同Class对象作对比。假如一个集合中有好几种类型,我们需要统计每种类型出现的次数,那么我们需要为每一种类型写一个if(x instanceof 类型x),这样非常麻烦而且注意,集合中的类型我们必须已知。
Java 1.1 为Class 类添加了 isInstance()方法。利用它可以动态调用instanceof 运算符。
Object o = pets.elementAt(i); //待判断的对象for (int j = 0; j < petTypes.length; ++j) //petTypes[]中是所有可能的类型if (petTypes[j].isInstance(o)) {String key = petTypes[j].toString();((Counter)h.get(key)).i++;}
RTTI语法:
Java 用Class对象实现自己的RTTI 功能。Class类也提供了 其他大量方式,以方便我们使用RTTI。
除了上面提到的forName()方法外,Class类还有以下几个方法比较常用:
- Class.getInterfaces方法会返回Class对象的一个数组,用于表示包含在Class对象内的接口。
- Class.getSuperclass()查询该对象的直接基础类是什么。
- Class.newInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别的。利用newInstance(),我们可在没有现成对象供“克隆”的情况下新建一个对象。
用 newInstance()创建的类必须有一个默认构建器。 没有办法用 newInstance()创建拥有非默认构建器的对象。
interface HasBatteries {}
interface Waterproof {}
interface ShootsThings {}
class Toy {Toy() {}Toy(int i) {}
}
class FancyToy extends Toy implements HasBatteries, Waterproof, ShootsThings {FancyToy() { super(1); }
}
public class ToyTest {public static void main(String[] args) {Class c = null; //class句柄try {c = Class.forName("FancyToy");} catch(ClassNotFoundException e) {}printInfo(c); //输出:“Class name: FancyToy is interface? [false]”Class[] faces = c.getInterfaces(); //获得实现的接口for(int i = 0; i < faces.length; i++)printInfo(faces[i]); //输出:“Class name: HasBatteries is interface? [true]”// “Class name: Waterproof is interface? [true]”// “Class name: ShootsThings is interface? [true]”Class cy = c.getSuperclass(); //获得基础类Object o = null;try {o = cy.newInstance(); // 用cy新建一个对象} catch(InstantiationException e) {}catch(IllegalAccessException e) {}printInfo(o.getClass()); //输出:“Class name: Toy is interface? [false] ”}//打印--通过getName()获得Class句柄的名字,并用interface()调查它是不是一个接口。 static void printInfo(Class cc) {System.out.println("Class name: " + cc.getName() " is interface? [" cc.isInterface()+ "]");}
}