之前看java编程思想第十四章类型信息,直接跳过了对RTTI概念的
理解,只看了
class、
instanceof等的用法,
发现这样的做法是不可取的,因为这样就只是会用这些提供的类而忽略了真正的原理。
RTTI假定我们在编译时已经知道了所有类型,然后RTTI才能在运行时进行正确性检查,这里是比较容易
误解的地方,举个
例子:
public class RuntimeClass {
public void test(){
List runtime = new LinkedList();
runtime.add(new RuntimeClass());
String runtimeStr = (String) runtime.get(0);
runtimeStr.toString();
}
public static void main(String[] args){
RuntimeClass runtimeclass = new RuntimeClass();
runtimeclass.test();
}
}
上图中我们先向list放入RuntimeClass类型的对象,然后通过强制转换变成String类型,在编译时成功生成class文件并且不报错。但是当我们运行main函数的时候,抛出了ClassCastException,这就说明了RTTI是在运行时才进行类型的正确性检查。在上图中模糊了list的类型(默认为object),举个不恰当的例子在编译的时候编译器只会看你对象设置的范围是否大于你要转换的范围,如果你要转换的类型在你对象设置的范围内,则编译通过,上面的例子还有一个小细节,向上转型的时候不需要显示的写出转换类型而向下转型的时候却要显示的类型,这是因为编译器知道向上转型的类型就是你要转的类型 而向下转型的类型是不确定的,所以编译器需要你显示的写出转换类型以便编译器进行检查是否
合理。其实这是向下转型的一种情况,只是先做了向上转型成Object类型,然后在向下转型时编译器识别object可以转换成Runtime(由于在编译器还拿不到实际引用的类型信息所以只能对显示类型进行检查),但是在运行时发现实际类型不对应才报错。
public void test(){
RuntimeClass runtimeClass = (RuntimeClass)"test";
}
如果像上图一样,直接进行不同类型的强制转换,编译肯定是不能通过的,因为两者类型没有任何关系,既不是向上转型也不是向下转型所以在编译期就会检查出来。
介绍了概念我们来介绍下类型信息的使用:
class对象
通过类加载器jvm为每个类生成一个class对象保存在.class文件中,这里的class对象就包含了与类有关的信息。class对象并不是一开始就存在的,而是在这个类第一次被静态引用(调用
构造器,构造器默认是静态的)时动态加载到jvm中,此class对象被用于所有目标类对象的创建。
class对象有几种方式获取:
1、getClass()方法
这种方式需要你拥有目标类的对象,runtime.getClass();这个方法是Object中的方法,它返回的是目标类的class对象。
2、 Class.forName()
Class.forName()是Class类的一个
静态方法,它并不需要目标类的对象即可加载类,一般用来静态语句的初始化; 它的输入是一个包含目标类名的字符串,返回的是目标类的引用;如果找不到指定路径的类,这个方法会抛出ClassNotFoundException。
这里额外介绍下ClassNotFoundException和NoClassDefFoundError的区别:
ClassNotFoundException是在使用常见的
三种方式(class类的forName方法、classloader类中的findSystemClass方法、classloader类中的loadclass方法)加载类的时候找不到需要加载的类,从名字可以看出这个是
异常可以被捕获处理;而NoClassDefFoundError则是编译时能够加载类,运行时发现类不见了属于
错误;
public class RuntimeClass {
class Circle extends RuntimeClassInterface{
}
public void test(){
try {
Class runtimeClass = Class.forName("Circle");
}catch (ClassNotFoundException e){
System.out.println("Not found Class !");
}
}
}
3、类字面常量
public void test(){
Class runtimeClass =RuntimeClass.class;
}
和forName、getClass的方式相比这种方式简单,而且更安全,因为他在编译时就会受到检查(因此不需要至于try语句块中),也不需要调用任
何方法,生成任何对象,所以效能也更高。
类的初始化:
static初始化是在类加载的时候进行的,通俗理解一下就是static块或者static属性只有在类第一次加载时才会被执行;
类字面常量这种方式不会自动初始化目标类,而forName为了得到class引用会先初始化目标类。如果一个static final值是“编译器常量”那么这个值不需要初始化就可以被读取,反过来读取这个值也不会引起类的初始化加载;如果将一个域(值是在第一次加载时才确定)设置成static final是不能确保这种行为的。如果一个static不是final,那么在读取之前要求先进行链接和初始化。
泛化的class引用
为了使class的引用更加具体可以通过
泛型进行类型
限制class<RuntimeClass>,也可以通过Class<?>
通配符来放松限制,通过泛型可以在编译器检查类型正确性。也可以组合使用 Class<? extends RuntimeClass>.
instanceof用于特定类型判断
public void test(){
RuntimeClass runtimeClass = new RuntimeClass();
if(runtimeClass instanceof RuntimeClass){
System.out.println(" This is true !");
}
}
动态instanceof
public void test(){
Class runtimeClass =RuntimeClass.class;
if( runtimeClass.isInstance(new RuntimeClass()) ){
System.out.println(" This is true !");
}
if( runtimeClass.isInstance( RuntimeClass.class) ){
System.out.println(" This is true !");
}
instanceof和Class的区别
instanceof在判断类型的时候范围是这个类的或者这个类的派生类,而用class进行==判断时 ,只判断是不是这个类不会判断是不是这个类的派生类。
反射
前面介绍的是在编译器已知的情况下,RTTI可以告诉你类型信息、检查你
类型转换的正确性,但是在有些特殊情况下,在编译时无法得知对象所属的类。反射提供了这种可能,通过反射告知jvm类信息。RTTI和反射的区别在于,RTTI可以在编译的时候打开和检查.class文件,而对于反射来说编译时获取不到.class文件,只能在运行时打开和检查。java通过class和java.lang.reflect实现反射。
public void test()throws Exception{
Class c = Class.forName("RuntimeClass");
Field field = c.getField("");
Constructor constructor = c.getConstructor(RuntimeClass.class);
Method method = c.getMethod("");
}
类型信息的应用 动态代理
由于java中存在三种形式的动态代理内容较多,所以放在《动态代理扩展》中讲解。
关于类型信息就介绍到这。欢迎评论指正写的不好的地方。