举例讲解Java的RTTI运行时类型识别机制
1、rtti:
运行时类型信息可以让你在程序运行时发现和使用类型信息。
在java中运行时识别对象和类的信息有两种方式:传统的rtti,以及反射。下面就来说下rtti。
rtti:在运行时,识别一个对象的类型。但是这个类型在编译时必须已知。
下面通过一个例子来看下rtti的使用。这里涉及到了多态的概念:让代码只操作基类的引用,而实际上调用具体的子类的方法,通常会创建一个具体的对象(circle,square,或者triangle,见下例),把它向上转型为shape(忽略了对象的具体类型),并在后面的程序中使用匿名(即不知道具体类型)的shape引用:
abstract class shape { // this 调用当前类的tostring()方法,返回实际的内容 void draw(){ system.out.println(this + "draw()"); } // 声明 tostring()为abstract类型,强制集成在重写该方法 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 static void main(string[] args){ // 把shape对象放入list<shape>的数组的时候会向上转型为shape,从而丢失了具体的类型信息 list<shape> shapelist = arrays.aslist(new circle(), new square(), new triangle()); // 从数组中取出时,这种容器,实际上所有的元素都当成object持有,会自动将结果转型为shape,这就是rtti的基本的使用。 for(shape shape : shapelist){ shape.draw(); } }
输出结果为:
circledraw() squaredraw() triangledraw()
存入数组的时候,会自动向上转型为shape,丢失了具体的类型,当从数组中取出的时候,(list容器将所有的事物都当做object持有),会自动将结果转型回shape,这就是rtti的基本用法。java中所有的类型转换都是在运行时进行正确性检查的,也就是rtti:在运行时,识别一个对象的类型。
上面的转型并不彻底,数组的元素取出时又object转型为shape,而不是具体的类型,编译时这是由容器和java泛型系统来确保这一点的,而在运行时时有类型转换操作来确保这一点的。
而能够通过shape对象执行到子类的具体代码就是又多态来决定的了,具体看shape引用所指向的具体对象。
另外,使用rtti,可以查询某个shape引用所指向的对象的确切类型,然后选择性的执行子类的方法。
2、class对象:
要了解rtti在java中的工作原理,必须知道类型信息在运行时是如何表示的,这里是由class这个特殊对象完成的。
class对象是用来创建类的所有的“常规”对象的。java使用class对象来执行其rtti。
每当编译一个新类,就会产生一个class对象(.class文件)。运行这个程序的jvm将使用“类加载器”这个子系统。
类加载器子系统:包含一条类加载器链,但只有一个原生类加载器,它是jvm实现的一部分。原生类加载器加载可信类,包括java api类,通常是从本地磁盘加载的。当需要以某种特定的方式加载类,以支持web服务器应用,可以挂接额外的类加载器。
2.1、加载类的时机:
当程序创建第一个对类的静态成员的引用时,就会加载这个类。这证明其实构造器也是类的静态方法,当使用new操作符创建类的新对象也会当做对类的静态成员的引用。
可见java程序时动态加载的,按需加载。需要用到class时,类加载器首先会检查这个类的class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找到.class文件。接下来是验证阶段:加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。
2.2、class相关方法,newinstance()
下面通过一个例子演示class对象的加载:
class a { // 静态代码库,在第一次被加载时执行,通过打印信息知道该类什么时候被加载 static { system.out.println("loading a"); } } class b { static { system.out.println("loading b"); } } class c { static { system.out.println("loading c"); } } public class load { public static void main(string[] args){ system.out.println("execute main..."); new a(); system.out.println("after new a"); try { class.forname("com.itzhai.test.type.b"); } catch (classnotfoundexception e) { system.out.println("cloud not find class b"); } system.out.println("after class.forname b"); new c(); system.out.println("after new c"); } }
输出结果为:
execute main... loading a after new a loading b after class.forname b loading c after new c
可见,class对象在需要的时候才被加载,注意到这里的class.forname()方法:
forname()方法是取得class对象的引用的一种方法,通过这个方法获得恰当的class对象的引用,就可以在运行时使用类型信息了。
如果你已经有了一个感兴趣的类型的对象,则可以通过跟类object提供的getclass()方法来获得class引用。
下面是一段class的使用的代码:
interface x{} interface y{} interface z{} class letter { letter(){}; letter(int i){}; } class newletter extends letter implements x, y, z{ newletter(){ super(1); }; } public class classtest { /** * 打印类型信息 * @param c */ static void printinfo(class c){ // getname()获得全限定的类名 system.out.println("class name: " + c.getname() + " is interface? " + c.isinterface()); // 获得不包含包名的类名 system.out.println("simple name: " + c.getsimplename()); // 获得全限定类名 system.out.println("canonical name: " + c.getcanonicalname()); } public static void main(string[] args){ class c = null; try { // 获得class引用 c = class.forname("com.itzhai.test.type.newletter"); } catch (classnotfoundexception e) { system.out.println("can not find com.itzhai.test.type.newletter"); system.exit(1); } // 打印接口类型信息 for(class face : c.getinterfaces()){ printinfo(face); } // 获取超类class引用 class up = c.getsuperclass(); object obj = null; try { // 通过newinstance()方法创建class的实例 obj = up.newinstance(); } catch (instantiationexception e) { system.out.println("can not instantiate"); } catch (illegalaccessexception e) { system.out.println("can not access"); } // 打印超类类型信息 printinfo(obj.getclass()); } }
输出为:
class name: com.itzhai.test.type.x is interface? true simple name: x canonical name: com.itzhai.test.type.x class name: com.itzhai.test.type.y is interface? true simple name: y canonical name: com.itzhai.test.type.y class name: com.itzhai.test.type.z is interface? true simple name: z canonical name: com.itzhai.test.type.z class name: com.itzhai.test.type.letter is interface? false simple name: letter canonical name: com.itzhai.test.type.letter
注意,在传递给forname()的字符串必须使用全限定名(包括包名)。
通过printinfo里面使用到的方法,你可以在运行时发现一个对象完整的类继承结构。
通过使用class的newinstance()方法是实现“虚拟构造器”的一种途径,用来创建class的实例,得到的是object引用,但是引用时指向letter对象。使用newinstance()来创建的类,必须带有默认的构造器。(而通过反射api,可以用任意的构造器来动态的创建类的对象)。
2.3、类字面常量:
除了使用getname()方法,java还提供了另一种方法来生成对class对象的引用,即使用类字面常量:
newletter.class;
此方法简单安全,编译时就受到检查,更高效。不仅可用于普通类,也可以用于接口,数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段type,type字段是一个引用,执行对应的基本数据类型的class对象。为了统一,建议都使用.class这种形式。
2.4、使用.class与使用getname()方法创建对象引用的区别:
使用.class创建时,不会自动的初始化class对象。创建步骤如下:
(1)加载 由类加载器执行:查找字节码(通常是在classpath指定的路径中查找,但并非必须的),然后从这些字节码中创建一个class对象。
(2)链接 将验证类中的字节码,为静态域分配存储空间,如果需要,将会解析这个类创建的对其他类的所有的引用。
(3)初始化 如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行的:
class data1{ static final int a = 1; static final double b = math.random(); static { system.out.println("init data1..."); } } class data2{ static int a = 12; static { system.out.println("init data2..."); } } class data3{ static int a = 23; static { system.out.println("init data3..."); } } public class classtest2 { public static void main(string[] args){ system.out.println("data1.class: "); class data1 = data1.class; system.out.println(data1.a); // 没有初始化data1 system.out.println(data1.b); // 初始化了data1 system.out.println(data2.a); // 初始化了data2 try { class data3 = class.forname("com.itzhai.test.type.data3"); // 初始化了data3 } catch (classnotfoundexception e) { system.out.println("can not found com.itzhai.test.type.data3..."); } system.out.println(data3.a); } }
输出的结果为:
data1.class: 1 init data1... 0.26771085109184534 init data2... 12 init data3... 23
初始化有效的实现了尽可能的“惰性”。
2.5、下面是判断是否执行初始化的一些情况:
(1)class语法获得对类的引用不会引发初始化;
(2)class.forname()产生了class引用,立即进行了初始化;
(3)如果一个static final值是“编译器常量”,那么这个值不需要对类进行初始化就可以被读取;
(4)如果只是把一个域设置为static final还不足以确保这种行为,例如上面的:
static final double b = math.random();
(5)如果一个static域bushifinal的,那么在对它访问时,总是要先进性链接和初始化;
2.6、泛化的class引用:
class引用表示的是它所指向的对象的确切类型,而该对象便是class类的一个对象。在javase5中,可以通过泛型对class引用所指向的class对象进行限定,并且可以让编译器强制执行额外的类型检查:
class intcls = int.class; // 使用泛型限定class指向的引用 class<integer> genintcls = int.class; // 没有使用泛型的clas可以重新赋值为指向任何其他的class对象 intcls = double.class; // 下面的编译会出错 // genintcls = double.class;
2.6.1、使用通配符?放松泛型的限定:
class<?> intcls = int.class; intcls = string.class;
在javase5中,class<?>优于平凡的class,更建议使用class<?>,即便它们是等价的,因为class<?>的好处是它表示你并非是碰巧或者疏忽,而是使用了一个非具体的类引用。
为了限定class的引用为某种类型,或者该类型的子类型可以将通配符与extends一起使用,创建一个范围:
class<? extends number> num = int.class; // num的引用范围为number及其子类,所以可以按照如下赋值 num = double.class; num = number.class;
2.6.2、泛型下的newinstance()方法:
使用了泛型后的class,调用newinstance()返回的对象是确切类型的,但是当你使用getsuperclass()获取泛型对应的超类的时候真正的类型会有一些限制:编译器在编译期就知道了超类的类型,但是,通过这个获取到的超类引用的newinstance()方法返回的不是精确类型,而是object:
dog dog = dogcls.newinstance(); abstract class animal { } class dog extends animal{ } // 下面的写法是错误的,只能返回 class<? super dog>类型 // class<animal> animalcls = dogcls.getsuperclass(); class<? super dog> animalcls = dogcls.getsuperclass(); // 通过获取的超类引用,只能创建返回object类型的对象 object obj = animalcls.newinstance();
2.6.3、新的转型语法:cast()方法
直接看下代码:
animal animal = new dog(); class<dog> dogcls = dog.class; dog dog = dogcls.cast(animal); // 或者直接使用下面的转型方法 dog = (dog)animal;
可以发现,使用cast()方法的做了额外的工作,这种转换方法可以用在一下的情况中:在编写泛型带的时候,如果存储了class引用,并希望通过这个class引用来执行转型,就可以使用cast()方法。
3、类型检查 instanceof
3.1、类型转换前先做检查
编译器允许你*的做向上转型的赋值操作,而不需要任何显示的转型操作,就好像给超类的引用赋值那样。
然而如果不使用显示的类型转换,编译器就不允许你执行向下转换赋值,这个时候我们不妨先来检查一下对象是不是某个特定类型的实例,使用到了关键字 instanceof:
if(x instanceof dog) ((dog) x).bark();
3.2、rtti的形式:
所以,到目前为止,我们知道rtti的形式包括:
(1)传统的类型转换 (shape)
(2)代表对象的类型的class对象
(3)关键字instanceof
3.3、动态的instanceof方法:
class.isinstance方法提供给了一种动态测试对象的途径。
下面演示下instanceof和class.isinstance的用法:
attribute:
public interface attribute { }
shape:
/** * 创建一个抽象类 */ public abstract class shape{ // this调用了当前类的tostring方法获得信息 public void draw() { system.out.println(this + ".draw()"); } // 声明tostring()方法为abstract,从而强制继承者需要重写该方法。 abstract public string tostring(); }
circle:
public class circle extends shape implements attribute{ public string tostring(){ return "circle"; } }
square:
public class square extends shape{ public string tostring(){ return "square"; } }
triangle:
public class triangle extends shape{ public string tostring(){ return "triangle"; } }
类型检查:
// instanceof circle c = new circle(); // 判断是否超类的实例 system.out.format("using instanceof: %s is a shape? %b\n", c.tostring(), c instanceof shape); // 判断是否circle的实例 system.out.format("using instanceof: %s is a circle? %b\n", c.tostring(), c instanceof circle); // 判断是否超类的实例 system.out.format("using class.isinstance: %s is a shape? %b\n", c.tostring(), shape.class.isinstance(c)); // 判断是否接口的实例 system.out.format("using class.isinstance: %s is a attribute? %b\n", c.tostring(), attribute.class.isinstance(c));
可以发现,instanceof或者class.isinstance方法判断了是否继承体系的实例,即除了判断本身,还判断是否超类或接口的实例。
下面演示下使用动态的class.instance的用法:
首先创建一个抽象的形状生成器类:
public abstract class shapecreator { private random rand = new random(10); // 返回一个对象类型数组,由实现类提供,后面会看到两种实现形式,基于forname的和基于类字面常量的.class public abstract list<class<? extends shape>> types(); // 随机生成一个对象类型数组中的类型对象实例 public shape randomshape(){ int n = rand.nextint(types().size()); try { return types().get(n).newinstance(); } catch (instantiationexception e) { e.printstacktrace(); return null; } catch (illegalaccessexception e) { e.printstacktrace(); return null; } } // 生成一个随机数组 public shape[] createarray(int size){ shape[] result = new shape[size]; for(int i=0; i<size; i++){ result[i] = randomshape(); } return result; } // 生成一个随机数组,泛型的arraylist public arraylist<shape> arraylist(int size){ arraylist<shape> result = new arraylist<shape>(); collections.addall(result, createarray(size)); return result; } }
接下来编写一个该抽象类的实现:
/** * forname的生成器实现 * @author arthinking * */ public class fornamecreator extends shapecreator{ private static list<class<? extends shape>> types = new arraylist<class<? extends shape>>(); private static string[] typenames = { "com.itzhai.javanote.entity.circle", "com.itzhai.javanote.entity.square", "com.itzhai.javanote.entity.triangle" }; @suppresswarnings("unused") private static void loader(){ for(string name : typenames){ try { types.add((class<? extends shape>)class.forname(name)); } catch (classnotfoundexception e) { e.printstacktrace(); } } } // 初始化加载所需的类型数组 static { loader(); } public list<class<? extends shape>> types() { return types; } }
最后写一个统计形状个数的类,里面用到了instanceof:
public class shapecount { static class shapecounter extends hashmap<string, integer>{ public void count(string type){ integer quantity = get(type); if(quantity == null){ put(type, 1); } else { put(type, quantity + 1); } } } // 演示通过instanceof关键字统计对象类型 public static void countshapes(shapecreator creator){ shapecounter counter = new shapecounter(); for(shape shape : creator.createarray(20)){ if(shape instanceof circle) counter.count("circle"); if(shape instanceof square) counter.count("square"); if(shape instanceof triangle){ counter.count("triangle"); } } system.out.println(counter); } public static void main(string[] args){ countshapes(new fornamecreator()); } }
改写一下抽象类的实现,重新用类字面常量实现:
/** * 字面量的生成器实现 */ public class literalcreator extends shapecreator{ public static final list<class<? extends shape>> alltype = collections.unmodifiablelist(arrays.aslist(circle.class, triangle.class, square.class)); public list<class<? extends shape>> types(){ return alltype; } public static void main(string[] args){ system.out.println(alltype); } }
现在使用class.instance统计形状的个数如下:
/** * 通过使用class.instanceof动态的测试对象,移除掉原来的shapecount中单调的instanceof语句 * */ public class shapecount2 { private static final list<class<? extends shape>> shapetypes = literalcreator.alltype; static class shapecounter extends hashmap<string, integer>{ public void count(string type){ integer quantity = get(type); if(quantity == null){ put(type, 1); } else { put(type, quantity + 1); } } } // 演示通过class.isinstance()统计对象类型 public static void countshapes(shapecreator creator){ shapecounter counter = new shapecounter(); for(shape shape : creator.createarray(20)){ for(class<? extends shape> cls : shapetypes){ if(cls.isinstance(shape)){ counter.count(cls.getsimplename()); } } } system.out.println(counter); } public static void main(string[] args){ countshapes(new fornamecreator()); } }
现在生成器有了两种实现,我们在这里可以添加一层外观,设置默认的实现方式:
/** * 现在生成器有了两种实现,我们在这里添加一层外观,设置默认的实现方式 */ public class shapes { public static final shapecreator creator = new literalcreator(); public static shape randomshape(){ return creator.randomshape(); } public static shape[] createarray(int size){ return creator.createarray(size); } public static arraylist<shape> arraylist(int size){ return creator.arraylist(size); } }
3.4、instanceof与class的等价性:
instanceof和isinstance()生成的结果完全一样,保持了类型的概念,判断是否一个类或者是这个类的派生类。
equals()与==也是一样的,而使用这个比较实际的class对象,就没有考虑继承。
system.out.println(new circle() instanceof circle); // true system.out.println(shape.class.isinstance(new circle())); // true system.out.println((new circle()).getclass() == circle.class); // true system.out.println((new circle().getclass()).equals(shape.class)); // false