学习内容:
- 传统的 RTTI
- Class 对象的加载
- 获取 Class 对象引用的方式
- ClassforName
- 类字面常量
- Class 引用的泛化
- instanceof 关键字
- 反射
- 相关类
- 简单使用示例
前言
运行时类型信息使得我们可以在程序运行时发现和使用类型信息,Java 中运行时识别对象和类的信息有如下两种方式:
- 传统的 RTTI :假定我们在编译时已经知道所有的类型
- 传统的类型转换。
- 通过查询 Class 对象获取运行时所需的信息
- 通过 instanceof 得知对象是否是某个特定类型的实例
- 反射技术:允许我们运行时发现和使用类型的信息
1. RTTI
RTTI,Run-Time Type information
1.1 为什么需要 RTTI?
面向对象编程中基本的目的是:让代码只操纵对基类的引用,这样如果要添加一个新类来扩展程序,就不会影响到原来的代码。
于是我们往往会创建一个具体对象,之后将其向上转型为基类型对象,并在后面使用该基类型对象引用,但此时该对象已经丢失了其具体类型,我怎么调用具体类型的方法呢?此时 RTTI 就起作用了。RTTI 会在运行时,识别一个对象的类型,得到引用指向的对象的确切类型。再之后就是多态的事情了,针对不同类型类型执行不同代码。
1.2 Class 对象
Java 使用 Class 对象来执行其 RTTI,那么 Class 对象是什么呢?
类是程序的一部分,每一个类都有一个 Class 对象。换言之,每当编写并编译了一个新类,就会在同名的 .class 文件中产生一个 Class 对象。Class 对象包含了与类有关的信息。那创建 Class 对象具体做什么呢?答案是通过 Class 对象创建我们需要的关于这个类的所有对象(比如对象实例,或者静态变量的引用值等)。实际上,所有的类都是在对其第一次使用时,动态加载到 JVM 中的,其中,“类加载器” 会负责将 Class 对象加载到内存中,而一旦加载成功之后,他就可以用来创建这个类的所有对象。
1.2.1 Class 对象的加载
前面我们提到过,所有的类都是在对其第一次使用时,动态加载到 JVM 中的。实际上,当创建第一个对类的静态成员的引用时,就会加载这个类。这点也证明构造器是类的静态方法,虽然其没有显式指明 static,即使用 new 操作符创建类的新对象也会被当作对类的静态成员的引用。
因此,Java 程序在开始运行之前并非被完全加载,其各个部分在必需时才进行加载。
当使用该类时,类加载器首先会检查这个类的 Class 对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 .class 文件,在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不含不良 Java 代码(这是 Java 安全防范的措施之一)。一旦某个类的 Class 对象被载入内存,那么它久被用来创建这个类的所有对象。
补充一点关于类加载器:
- 类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是Java 实现的一部分,用来加载 可信类,包括 Java API 类,通常是从本地盘中加载的。
- 如果有特殊需求(如以某种特殊的方式加载类,以支持 Web 服务器应用),那么可以挂接额外的类加载器,不过通常不需要添加额外的类加载器。
下面给出一个例子,证明加载的时间点:
class Candy {
static { System.out.println("Loading Candy"); }
}
class Gum {
static { System.out.println("Loading Gum"); }
}
class Cookie {
static { System.out.println("Loading Cookie"); }
}
public class SweetShop {
public static void print(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) {
print("inside main");
new Candy();
print("After creating Candy");
try {
Class.forName("Gum");
} catch(ClassNotFoundException e) {
print("Couldn't find Gum");
}
print("After Class.forName(\"Gum\")");
new Cookie();
print("After creating Cookie");
}
}
/*输出
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
Process finished with exit code 0
*/
从结果上看,通过 new 创建 Candy 和 Cookie 的对象时,二者的 Class 对象被加载,也证明了 Class 对象仅在需要的时候才被加载。
比较特殊的一句代码:
Class.forName("Gum")
其中 forName 是 Class 类(所有 Class 对象都属于这个类)的一个 static 成员,是取得 Class 对象的引用的一种方法。此处并未使用获取返回的 Class 对象引用,它的作用在于要求 JVM 查找并加载类,加载的过程中,Gum 的 static 子句被执行,Gum 类即被加载。
Class 对象和其他对象一样,我们可以获取并操作它的引用
1.2.2 获取 Class 对象的引用
无论何时,只要我们想在运行时使用类型信息,就必须首先获得对恰当的 Class 对象的引用。本小节就来介绍一下三种获取 Class 对象引用的方式,及其要点。
1.2.2.1 Class.forName()
forName() 是取得 Class 对象的引用的一种方法。它接收一个包含目标类的文本名的 String 作为输入参数,返回一个和 Class 对象的引用。
该方法会要求 JVM 查找并加载指定的类,然后 JVM 会执行静态代码段,之后返回 Class 对象的引用
如果 Class.forName 找不到要加载的类,会配出 ClassNotFoundException 异常。