RTTI

RTTI 翻译过来是运行时类型信息。一个引用不仅可以指向和自己类型一致的对象,还可以指向自己子类的对象。那么JVM在执行代码时是如何判定引用指向的对象是否合法?这时就需要用到RTTI。

一个小案例


class Base {}

class Foo {}

public class Test{
	public static void main(String[] args) {
		Base base = new Base();
		Object obj = base;
		Foo foo = (Foo) obj;    //运行时抛异常
	}
}

上的代码编译时会通过,但是运行时会抛出ClassCastException异常。那么问题来了,编译时为啥通过了?JVM虚拟机咋就是知道强转出现问题了?首先分析代码

Base base = new Base();
Object obj = base;
Foo foo = (Foo) obj;

第一行创建了一个Base对象,定义一个Base引用,将该引用指向创建的对象。这行代码是没有什么问题,因为引用的类型和对象的类型是一致的。

第二行代码将Base型引用赋值给一个Object引用,实质是让obj指向base指向的对象。这是向上转型,没有问题,当然编译器会对向上转型做检查的。

第三行代码就出现问题了,它实质是让一个Foo类型引用指向了一个Base对象,这是不合理的。因为从类的声明上来看这个两个类半毛钱关系都没有。

分析完毕后我们来分析一下为啥编译能通过。首先一行是没有异议的。第二行是编译器会查看类的继承,它发现base是Base类型,而obj是Object类型,从继承角度来讲将子类引用赋值给父类引用是没有问题的。第三行代码对编译器来说也是没有问题了,因为无论是向上还是向下转型,编译器只看继承关系,如果存在继承关系那么编译器会放过的,Object是所有类的基类,所以没有毛病。编译器其实只能做一些简单的类型检查,即它只判断赋值号左边的类型和右边类型是否一致或存在某种关系,它不能透过现象看本质。

JVM是如何知道转型出现了问题了?它查看了obj的RTTI(通俗的讲它查看了obj指向对象的类型)发现和foo变量的类型不一致,所以它认为强转出现了问题,实际上我们不能将Foo引用指向Base对象。

如何查看RTTI

查看一个引用的RTTI和class对象密不可分,这个对象是一个特殊对象,和类同生共死。class对象中包含了类的所有信息,比如类名。当我们编译一个类后,编译器会生成相应的class对象,并将该类和class对象一同写到了.class文件中。当类加载器加载类时会连同class对象一同加载到内存。而当我们创建对象时,JVM虚拟机是根据class对象创建出普通对象。因此,普通对象中将会持有class对象的引用。现在事情就很简单了,JVM通过引用找到普通对象,通过普通对象中的引用找到class对象,通过class对象查到了类信息,这时一个引用的RTTI就被获得了。

获取RTTI

RTTI用于描述class对象信息,Java中也为程序员提供了获取RTTI的接口

Object obj = new Base();
Class<?> c = obj.getClass();  //返回class对象引用

上面的代码我们获取了class对象,而obj的RTTI就在c中

前面提到对象都是通过class对象创建出来的,因此我们可以采用别的方式来创建对象,下面是一个小例子:

Class<?> baseClass = Class.forName("Base"); //返回class对象引用
Base base = baseClass.newInstance();

RTTI的表现形式

以下行为会查看RTTI:

  • 向下转型,A a = (B) b;
  • 获取Class对象,Class<?> c = a.getClass();
  • 对于关键字instanceof的使用,instanceof主要用来查看一个对象是否属于某个类

反射

要讲反射首先要将RTTI和反射的应用场景搞清楚。反射和RTTI是没有直接关系的,只不过因为反射也涉及到类型信息才放到这里讲。
RTTI的应用场景是这样的:给一个引用,然后识别这个引用的类型信息,它需要建立在类已知的基础上,即编译器见过这个类,那么JVM或者在我们代码中才能获取一个引用的RTTI。

通过反射也能获取类型信息,它是在我们不知道类信息的基础上来做的。比如某个类没有在本地存储,而是在将来的某个时刻会通过网络发送过来。那么我们在代码中无法通过new的方式来创建这个类的对象,也不能调用其方法(如果这样做,编译器会报错说找不到该类),这时就需要用到反射了。

下面举一个简单的例子来查看类信息

package xdysite.cn;

import java.lang.reflect.Method;

class Base {
	public void b() {
		System.out.println("It is Base class");
	}
}

public class Test{
	public static void main(String[] args) {
		try {
			//获取class对象
			Class<?> c = Class.forName("xdysite.cn.Base");
			//获取内部方法b
			Method m = c.getMethod("b");
			//创建普通对象
			Object obj = c.newInstance();
			//调用该方法
			m.invoke(obj);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

注意:虽然Base和Test在同一个包内,但是要调用Base中的方法,其权限必须是public,否则会报找不到该方法

http://www.cnblogs.com/lzq198754/p/5780331.html

小结

RTTI和反射都是获取类型信息,但是两个的应用场景是不一致的,所以在理解的时候要分清楚这一点

posted @ 2017-06-08 11:14  被罚站的树  阅读(3727)  评论(0编辑  收藏  举报