Java在运行时识别对象和类的信息有两种方式:
(1)传统的RTTI,假定在编译时已经知道了所有类型;
(2)反射机制,允许在运行时发现和使用类的信息。
1.为什么需要RTTI
在Java中,所有的类型转换都是在运行时进行正确性检查。
2.Class对象
Java使用Class对象来执行其RTTI;每当编写并编译了一个新类,就会产生一个Class对象(被保存在一个同名的.class文件中)
所有的类都是在对其第一次使用时,动态加载到JVM中的。程序创建第一个对类的静态成员的引用时,就会加载这个类。构造器也是类的静态方法,new操作符创建类的新对象也会被当做对类的静态成员的引用。
Java程序在开始运行之前并非被完全加载,各个部分是在必须时才加载的。(C++为静态加载语言)
Class.forName("类名");
//此方法为Class类的一个static成员。是取得Class对象的引用的一种方法。
//如果找不到要加载的类,会抛出ClassNotFoundException异常。
//只要想在运行时使用类型信息,就必须首先获得对恰当Class对象的引用。Class.forName()就是便捷途径
//区分getClass():他将返回表示该对象的实际类型的Class引用。
Class类包含的常用方法(设有类com.dao.Test(Test.class)):
c.getName():com.dao.Test
c.isInterface():false
c.getSimpleName():Test
c.getCanonicalName():com.dao.Test
c.getInterfaces():返回对象中包含的接口
c.getSuperclass():返回直接基类
c.newInstance():实现“虚拟构造器”的一种途径,创建的类必须带有默认构造器
2.1类字面常量
Test.class;//生成对Class对象的引用,在编译时就会受到检查(不需至于try中)根除对forName()的调用,
更高效不仅可以应用于普通类,也可应用于接口、数组及基本数据类型。
仅使用.class语法获得对类的引用不会引发初始化。但为了产生Class引用,Class.forName()立即就进行了初
始化。
如果一个static final值是“编译期常量”,那么这个值不需要对类进行初始化就可被读取。如果一个static域不是final的,那么在对他访问时,要先进行链接(为域分配存储空间)和初始化(初始化存储空间)。
import java.util.*;
class Initable {
static final int staticFinal = 47;//编译期常量
//非编译期常量
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws Exception {
Class initable = Initable.class;
System.out.println("After creating Initable ref");
// Does not trigger initialization:
System.out.println(Initable.staticFinal);
// Does trigger initialization:
System.out.println(Initable.staticFinal2);
// Does trigger initialization:
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
} /* Output:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
*///:~
2.2泛化的Class引用
Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
通过使用泛型语法,可以让编译器强制执行额外的类型检查。
Class<? extends Number> test = int.class;
test = double.class;
// ? :通配符
//向Class引用添加泛型语法的原因仅仅是为了提供编译器类型检查,此时newInstance()将返回该对象的确切类
//型。一个例外:超类 Class<? super xxx>
2.3新的转型语法
cast()方法;
3.类型转换前先做检查
编译期,若不使用显示的类型转换,编译器不允许执行向下转型赋值。进行向下转型前,若无其他信息告知对象类型,则使用instanceof是非常必要的,否则会得到一个ClassCastException异常。
if(x instanceof Dog)
((Dog)x).bark();
3.1使用类字面常量
6.反射:运行时的类信息
RTTI可以获得某个对象的确切类型,但有个限制:这个类型在编译时必须已知。
反射提供了一种机制——用来检查可用的方法,并返回方法名。Java通过JavaBeans提供了基于构件的编程架构
Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含了Field、Method及Constructor类(每个
类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里的成员
(1)使用Constructor创建新对象
(2)用get()和set()方法读取和修改与Field对象关联的字段
(3)用invoke()方法调用与Method对象关联的方法。
(4)可以调用getFields()、getMethods()、getConstructors()等方法,返回表示字段、方法及构造器的对象的数
组。
未知类型对象所属的类的.class文件对于JVM来说必须是可获得的。
RTTI与反射区别:
RTTI,编译器在编译时打开和检查.class文件;
反射,在运行时打开和检查.class文件(编译时不可获取)。