Java中的RTTI与反射
个人理解,可能有误。理解自《java编程思想》。
首先,什么是RTTI?
RTTI(run-time type information)指的是Java在运行时能够获得或判断某个对象的类型信息。以Shape, Circle, Squre, Triangle为例,后三者继承shape。
主要有三种方式:
(1)转型:(Type) realType
Java中,允许自由的upcast,但是对downcast要求必须显示的指明,且编译器会检查这种downcast是否合理。而一旦通过编译,那么Java自然就知道了其类型信息了。
例:
Shape myShape = new Circle( ); => 编译之后,Java就知道myShape所指向对象的实际类型是Circle,而外观类型是Shape Circle myCircle = (Circle) new Shape( ); // 需显示声明的downcast
(2)利用class对象。
对于某个对象,可以利用Object.getClass()方法来获得其实际类型的class对象的引用。class对象有了,那么类型信息自然也就知道了。而且Object.getClass()方法返回的是对象实际类型的class对象,而非引用类型。
例:
List<Object> myList = new ArrayList<Object>(); Integer obj = new Integer(5); myList.add(obj); Class myClass = myList.get(0).getClass(); System.out.println(myClass.getName());
上面几行代码的输出结果是:java.lang.Integer。而Java之所以知道obj的实际类型是Integer,猜测应该是因为在编译时,Java已经知道了obj所指对象的实际类型了,然后在这个堆上的对象的内部,可能有个类似指针的东西,这个指针指向了实际的类型的Integer的class对象,故虽然后来又经历了擦除、转型,但是这个指针始终是指向Integer的class对象的,所以在运行时,随时能获得obj所指对象的实际类型。
(3)利用操作符instanceof或方法isInstance()来在运行时判断某个对象的类型信息。
(顺便说说class对象,Class对象是一类特殊的对象,它包含着与类有关的信息。当编写完某个.java文件,并编译之后,就会产生一个Class对象,由编译器偷偷的把这个class对象保存在编译后的.class文件中,当这个.class文件被加载到内存之后,随之会在内存中创建这个Class对象了。所以,获得class对象的前提是能获得类编译后的.class文件。且class对象的功能强大,方法众多:Class.getInterfaces()、Class.getSuperclass()、Class.getName()、Class.isInterface()等等,能用来获得很多信息。)
关于RTTI有一条前提就是某个对象的类型信息在编译时必须已知。RTTI的实质就是编译器在遍历检查代码时偷偷将类型信息记录下来并存储,以在运行时能够获得。现在考虑另一种情况:从磁盘或网络中获取了一串字节,并被告知这串字节代表一个类,也就是这个类是在编译之后得到的,在编译时对这个类一无所知,那么要如何使用这个类?这就需要反射机制。注意体味下RTTI与反射的不同,一个是编译期确定并知晓的,一个是在运行期才知晓的。
在Java中,Class对象与java.lang.reflect类库一起对反射的概念进行了支持。通过反射机制,匿名对象的类信息能在运行时被完全确定下来,尽管在编译时对其一无所知。反射也是需要使用class对象的,所以又回到了获得class对象的前提:先加载.class文件。所以使用反射机制与一个未知类型的对象打交道时,其类的.class文件对于JVM来说必须是可获取的,要么在本机上,要么通过网络取得。故RTTI与反射在最底层的思想上是很类似的,本质的区别在于:对RRTI,编译器在编译时打开和检查.class文件,而对反射来说,.class文件在编译时是不可获取的,在运行时未知对象已经来了再去打开和检查.class文件。
反射机制使得java能够创建一个在编译时完全未知的对象。
一个反射的例子:
Class<?> c = Class.forName(args); Method[] methods = c.getMethods(); Constructor[] constructors = c.getConstructors();
在编译时,对于args的具体类型并不清楚,当实际运行时,根据输入的args来寻找并加载需要的.class文件,获得对应的Class对象,然后利用这个Class对象就能获得其类信息,就能做很多事了。比如调用其构造器动态的创建对象,并调用此对象的某个方法。
猜测反射的主要作用可能是为了编写更加通用的代码,对于同一套操作逻辑,只要编写一段代码即可。例如将水果剥皮这个逻辑,在编写时,并不知道对哪种水果进行剥皮,也不知道应该怎样剥皮,这些都要等到运行时动态的确定。一段代码编译运行之后,传入苹果,那它就找apple.class,获取其class对象,再调用苹果自身的剥皮方法。而这段代码同样适用于香蕉的剥皮。
JavaBean中的setter与getter方法等,都是通过反射机制来实现的。在java web中,反射应用的非常多。