有两种形式在运行时获取类型信息:
- 传统的RTTI
- 反射
Class对象
- 运行时的类型信息是通过Class对象表现的,它包含了类的信息。所有“普通的”对象都是通过Class对象创建的。Java通过Class对象实现RTTI。
- 在你的程序中每个类都对应一个Class对象。每次编写并编译一个新的类时都会创建一个相应的Class对象,并且以.class文件储存起来。JVM用class loader子系统创建对象。
- JVM有一个原生的class loader用来加载“可信类”,包括JAVA API中的类。你也可以实现自己的带有特殊目的的class loader并与原生的串联起来。
- 在一个类第一次被访问static成员时,这个类则被加载到JVM。构造函数也是静态的,所以用new创建对象时也算访问了static成员。因此Java程序运行前并不是完全加载了。
- class loader先检查类型的Class对象是否加载了,如果没加载,class loader会找到.class文件(其他的class loader可能会从别处加载二进制码,比如从数据库)。字节被加载之后会被验证一下有没有损坏或者有没有不安全的代码。
- Class对象被加载到内存之后,将被用来创建这个类型的所有对象。
- 所有Class对象都属于Class类,和其他类一样可以使用引用操作Class对象。一种获得Class对象的方法是调用静态的forName()方法,这个方法接收一个字符串类型,是类的名字,需要使用包括报名的全名。如果找不到.class文件,会抛出一个ClassNotFoundException。
- 在运行时,使用类型信息之前要先获取Class对象。
Class.forName()
是比较方便的做法。如果已经有一个这个类型的对象了,可以调用对象getClass()
方法,这个方法是在Object里定义的。
Class literals
除了forName()
外,Java还提供一种获得Class对象引用的方法,比如Toy.class
。这种方法更简洁和更安全,因为这种方法的类型在编译时就会被检查。
基本类型的包装类有一个标准字段TYPE,提供了基本类型的Class对象,例如:boolean.class <=> Boolean.TYPE
。
__使用.class获得Class对象引用不会初始化Class对象__。获得一个可用的类需要三步
- 加载,由类加载器完成。找到字节码并创建一个Class对象。
- 链接,检验字节码,为static字段分配存储空间,解决其对他类的引用。
- 初始化,如果有父类则初始化父类,执行静态初始化器和静态初始化区块
直到第一次访问静态成员时初始化才执行。
用Class.forName()
获得引用则会引起初始化。
如果static final成员是编译时常量,访问这个成员则不会引起初始化。如果static final成员不是编译时常量(由初始化器赋值),也会引起初始化。访问不是final的static成员总会引起链接和初始化,即分配存储和初始化值。
Generic class references
Class<?> <=> Class
使用generic语法可以让你在编译时就发现类型不对的错误。只用Class的话,如果犯了错误到运行时才会发现。
New cast syntax
//: typeinfo/ClassCasts.java
class Building {}
class House extends Building {}
public class ClassCasts {
public static void main(String[] args) {
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b);
h = (House)b; // ... or just do this.
}
} ///:~
Checking before a cast
RTTI的三种形态:
- 传统的类型转换,使用RTTI来确保类型转换时正确,如果不正确会抛出ClassCastException异常
- 以Class object表示对象的类型。Class对象可以提供很有用的运行时信息。
- instanceof,检验对象是不是某类型。(动态Class.islnstance())
反射
RTTI可以提供类型信息,但是如果在编译时并不知道对象所属类的信息(比如从硬盘或网络读取的字节所组成的类),则无法使用RTTI。
Class类和java.lang.reflect
中的Field, Method, and Constructor(实现了Member接口)一起支持了反射的概念。在运行时,JVM会创建这些类型的对象来表示未知类的成员。
使用反射前提是JVM可以找到未知类型的.class文件(从硬盘中的文件或者网络)。
- RTTI在编译时,由编译器打开和检查.class文件。
- 在反射机制中,.class在编译时是不可用的,.class是由运行环境在运行时打开和检查的。
接口和类型信息
利用反射可以访问对象的方法和成员变量,不论访问修饰符是什么。但是反射不能修改final的变量,在运行时试图修改final字段不会报错,但是实际上修改并没有生效。
概念
class | type | .class文件 | Class | Class object | Class reference |
---|---|---|---|---|---|
java中的类 | 类型,对应于一个类,每个类都是一个类型 | 用来储存Class对象,程序中编写的每个类,都会被编译成一个.class文件 | 一种叫“Class”的类 | Class类的一个实例,这个类由.class文件而来,负责创建它所表示的类的实例 | Class object的引用,包括了这个类的statics |
其他小知识
- 一个类默认的构造器和这个类有相同的访问修饰符
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。