反射简介
反射允许我们在程序运行时获取和使用类的信息。
Class 对象
Java程序运行时,用Class对象表示类型信息,它包含了与类相关的信息。类是程序的一部分,每个类都有一个Class对象,也就是说,当一个类被编译之后,就会产生一个Class对象,并保存在.class文件中。当程序创建第一个对类的静态成员的引用时,JVM就会用类加载器加载Class对象,当该类的Class对象被加载入内存后,就可以使用它来创建实例对象。
获取Class对象引用的方式:
- 通过Class.forName() 方法来加载类并获取Class对象的引用并初始化该类,该方法的参数必须为类的全限定名(包含包名)
try {
Class.forName("Person");
} catch (Exception ex) {
ex.printStackTrace();
}
- 如果已经获得该类的实例,可通过该实例调用getClass()方法获取Class对象的引用
Person person = new Person();
Class clazz = person.getClass();
- 使用类字面常量,该方式仅仅只是拿到Class对象的引用,并没有进行初始化
Class clazz = Person.class;
对于基本数据类型,同样存在类字面常量,而且,在对应的包装类中有一个字段TYPE作为基本数据类型的类字面常量的引用,比如,对于 int 来说,其字面常量为int.class,它与Integer.TYPE等价,但要注意的是,它们与Integer.class是两回事。
为了使用类而做的准备工作有三个步骤:
1.加载。由类加载器执行,查找字节码,并通过字节码创建一个Class对象。
2.链接。在这个阶段将验证类中的字节码,为静态域分配存储空间,如果必须的话,还要解析这个类创建的对其他类的引用
3.初始化。如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块。初始化被延迟到了对静态方法(在这种情况下,构造器可以视为静态的方法)或者非常数静态域进行首次引用的时候才执行。例子如下:
import java.util.Random;
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);
//这里触发初始化,staticFinal2并不是一个编译期常量
System.out.println(Initable.staticFinal2);
//这里同样会触发初始化, 因为如果static域不是final的情况下,对它进行访问时必须要先进行链接(分配存储空间)和初始化(初始化存储空间)
System.out.println(Initable2.staticNonFinal);
//Class.forName()方法在加载类后会进行初始化
Class.forName("Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
输出结果如下:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
Class对象一些常用方法:
- newInstance()
创建新实例对象,使用该方法来创建的类,必须带有默认的构造器,否则会抛出异常InstantiationException - getName(), getSimpleName(), getCanonicalName()
getName() 获取全限定的类名,getSimpleName() 获取不含包名的类名, getCanonicalName() 也是获取全限定的类名, 对于普通的类来说,两者结果基本是一样的,但是对于一些特殊的类,比如数组和内部类等,两者的结果有所差异。
public class Main {
public static void main(String[] args) {
Integer[] array = new Integer[5];
InnerClass innerClass = new InnerClass();
System.out.println(array.getClass().getName());
System.out.println(array.getClass().getCanonicalName());
System.out.println(innerClass.getClass().getName());
System.out.println(innerClass.getClass().getCanonicalName());
}
private static class InnerClass {
}
}
结果如下:
[Ljava.lang.Integer;
java.lang.Integer[]
Main$InnerClass
Main.InnerClass
- isInstance()
和instanceof
关键字作用一样,但是更灵活,instanceof
需要显示地指定类型,而isInstance()
则不需要,只要拿到Class对象的引用即可。
对于两个Class对象的比较,可以使用==
也可以使用equals()
,但是它们与isIntanceof
以及isInstance()
有所不同,==
和equals()
不考虑继承
class Base {}
class Derived extends Base {}
public class ClassCompareTest {
static void test(Object x) {
Class clazz = x.getClass();
String className = clazz.getSimpleName();
System.out.println("测试" + clazz);
System.out.println("x instanceof Base [" + (x instanceof Base) + "]");
System.out.println("x instanceof Derived [" + (x instanceof Derived) + "]");
System.out.println("Base.class.isInstance(x) [" + (Base.class.isInstance(x)) + "]");
System.out.println("Derived.class.isInstance(x) [" + (Derived.class.isInstance(x)) + "]");
System.out.println(className + ".class == Base.class [" + (clazz == Base.class) + "]");
System.out.println(className + ".class == Derived.class [" + (clazz == Derived.class) + "]");
System.out.println(className + ".class.equals(Base.class) [" + (clazz.equals(Base.class)) + "]");
System.out.println(className + ".class.equals(Derived.class) [" + (clazz.equals(Derived.class)) + "]");
}
public static void main(String[] args) {
test(new Base());
test(new Derived());
}
}
输出结果为:
测试class Base
x instanceof Base [true]
x instanceof Derived [false]
Base.class.isInstance(x) [true]
Derived.class.isInstance(x) [false]
Base.class == Base.class [true]
Base.class == Derived.class [false]
Base.class.equals(Base.class) [true]
Base.class.equals(Derived.class) [false]
测试class Derived
x instanceof Base [true]
x instanceof Derived [true]
Base.class.isInstance(x) [true]
Derived.class.isInstance(x) [true]
Derived.class == Base.class [false]
Derived.class == Derived.class [true]
Derived.class.equals(Base.class) [false]
Derived.class.equals(Derived.class) [true]
类成员
通过Class对象可以获取类运行时的类成员信息,类成员用Member接口表示,该接口的实现类有Field、Method、 Constractor。
Field类
- Field对象的获取
Field表示类的成员变量,可以使用Class对象的getFields()
和getDeclaredFields()
,两者的区别在于前者只能拿到所有从父类继承来的public成员变量以及自己的public成员变量,而后者只能拿在本类内声明的所有成员变量,不管修饰符是什么。另外还有getField()
和getDeclaredField()
用来返回特定的成员变量,区别和前面一样。对于Method类以及Constractor类也同理。 - 获取成员变量的类型
可以使用getType()
和getGenericType()
获取成员变量的类型,两者的区别:- 前者返回的是变量类型的Class对象引用,不包含泛型信息
- 当变量类型没有泛型时,后者结果与前者一致。当变量类型有泛型时,返回结果是一个Type接口的类型,包含泛型信息。
假设有这样一个类:
运行下面例子:public class Student { private String name; private List<String> courses; }
Class clazz = Student.class; Field[] fields = clazz.getDeclaredFields(); for (Field field: fields) { System.out.println("getType(): " + field.getType()); System.out.println("getGenericType(): " + field.getGenericType()); }
结果如下:
getType(): class java.lang.String
getGenericType(): class java.lang.String
getType(): interface java.util.List
getGenericType(): java.util.List<java.lang.String>
参考:《Java编程思想(第4版)》Bruce Eckel 著