运行时识别对象和类的信息,主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
RTTI
RTTI的含义:在运行时,识别一个对象的类型
RTTI最基本的使用形式:所有的类型转换都是在运行时进行正确性检查的。
Class对象
Class对象结束用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。
类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的JVM将使用“类加载器”。
类加载器可以包含一条类加载器链,但只有一个原生类加载器。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常是从本地盘加载的。
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时(构造器也是类的静态方法,使用new操作符创建类的新对象也会被当作对类的静态成员的引用),就会加载这个类。
动态加载:Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。下面的示范程序可以证明这一点。
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 main(String[] args){
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try{
Class.forName("Gum");
}catch (ClassNotFoundException e){
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
/* Output:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*/
/*
Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的。
*/
forName()方法是Class类(所有Class对象都属于这个类)的一个static成员。forName()是取得Class对象的引用的一种方法。它是用一个包含目标类的文本名(注意拼写和大小写)的String作输入参数,返回的是一个Class对象的引用。对forName()的调用的“副作用”:如果类还没被加载就加载它。
想要在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径。也可以通过调用getClass()方法来获取Class引用,这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用。Class包含很多有用的方法,下面是其中的一部分:
package typeinfo;
interface HasBatteries{}
interface Waterproof{}
interface Shoots{}
class Toy{
Toy(){}
Toy(int i){}
}
class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
FancyToy(){
super(1);
}
}
public class ToyTest {
static void printInfo(Class cc){
System.out.println("Class name: "+cc.getName()+" is interface? ["+cc.isInterface()+"]");
System.out.println("Simple name : "+cc.getSimpleName());
System.out.println("Canonical name : "+cc.getCanonicalName());
}
public static void main(String[] args){
Class c=null;
try{
c=Class.forName("typeinfo.FancyToy");
}catch (ClassNotFoundException e){
System.out.println("Couldn't find FancyToy");
System.exit(1);
}
printInfo(c);
for(Class face : c.getInterfaces()){
printInfo(face);
}
Class up =c.getSuperclass();
Object obj=null;
try{
obj=up.newInstance();
}catch (InstantiationException e){
System.out.println("Cannot instantiate");
System.exit(1);
}catch (IllegalAccessException e){
System.out.println("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
/* Output:
Class name: typeinfo.FancyToy is interface? [false]
Simple name : FancyToy
Canonical name : typeinfo.FancyToy
Class name: typeinfo.HasBatteries is interface? [true]
Simple name : HasBatteries
Canonical name : typeinfo.HasBatteries
Class name: typeinfo.Waterproof is interface? [true]
Simple name : Waterproof
Canonical name : typeinfo.Waterproof
Class name: typeinfo.Shoots is interface? [true]
Simple name : Shoots
Canonical name : typeinfo.Shoots
Class name: typeinfo.Toy is interface? [false]
Simple name : Toy
Canonical name : typeinfo.Toy
*/
类字面常量
使用类字面常量来生成对Class对象的引用。
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示:
···等价于··· | |
---|---|
boolean.class | Boolean.TYPE |
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
注意:当使用“.class”来创建对Class对象的引用时不会自动地初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤:
- 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
- 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
- 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行:
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");
System.out.println(Initable.staticFinal);
System.out.println(Initable.staticFinal2);
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
*/
RTTI形式包括:
- 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出了一个ClassCastException异常。
- 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
- 关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。