在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为反射机制。
Class 对象
Java 中每个类都有自己的 Class 对象,为了生成这个类的对象,JVM 会使用类加载器来完成这个操作。
所有的类都是在第一次使用的时候加载到 JVM 中的,也就是当程序创建第一个对类的静态成员的引用的时候,就会加载这个类。
另外有一点,构造器也是类的静态方法,即使构造器方法的前面并没有使用 static 关键字。所以使用 new 来初始化类的时候也会被当做对类的静态成员的引用。
生成 Class 对象的引用
有两种方法来生成 Class 对象的引用,一种是使用 Class.forName()
方法,另外一种是类字面常量,比如 TestClass.class
。
forName 方法的调用会触发对应的类的加载过程,但使用类字面常量方式的时候,则不会自动初始化该 Class 对象。
类在初始化的时候分三个步骤:
- 加载。由类加载器执行,该步骤将查找字节码,并从中创建一个 Class 对象
- 链接。链接阶段将验证类中的字节码,为静态域分配空间,并且如果必要的话,解析这个类创建的对其他类的所有引用
- 初始化。如果该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化块
JVM 会将整个初始化操作延迟到对静态方法(构造器是隐式的静态方法)或者非常数静态域进行首次引用时才执行。
下面这个示例说明了哪些情况下会触发类的初始化,哪些不会:
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("cn.bluecoder.typeinfo.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
由上面的代码输出可以看到,如果一个 static final 值是“编译器常量”,就像 Initable.staticFinal
那样,那么这个值不需要对 Initable 类进行初始化就可以被读取。但不意味着只要是 static final 就可以被直接读取,比如 Initable.staticFinal2
的访问将强制进行类的初始化,因为它不是一个编译器常量。
如果一个 static 域不是 final 的,那么对它进行访问之前,总会先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)的操作,就像 Initable2.staticNonFinal
一样。
泛化的 Class 引用
单纯使用 Class
来操作可能很容易产生运行时问题,因为普通的类引用可以被重新赋值为指向任何其他的 Class 对象。通过泛型语法,可以把一部分检查让编译器来做掉。
下面的例子颇有代表性,具体的注意点在注释中说明:
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 GenericToyTest {
public static void main(String[] args) throws Exception {
Class ftClass = FancyToy.class;
FancyToy fancyToy = ftClass.newInstance();
// 只能用 Class<? super FancyToy>,而不能直接使用 Class
Class<? super FancyToy> up = ftClass.getSuperclass();
// 因为 ? super FancyToy 表示的是"某个类,它是 FancyToy 的超类",所以 newInstance() 返回的是 Object
Object obj = up.newInstance();
}
}
类型转换前的检查
形式有三种:
- 传统的类型转换,如
(Shape) obj
,如果执行了错误的转换,会抛出ClassCastException
- 查询对应的 Class 对象中的内容
- instanceof,分别为
obj instanceof 命名类型
和Class.isInstance(obj)
两种
注意 instanceof 不能与 Class 比较。
动态代理
Java 中动态代理使用反射来实现,它可以动态地创建代理并动态地处理对所代理方法的调用。
动态代理上所有的调用都会被重定向到单一的调用处理器上,作用是确定调用的类型和相应的对策。示例:
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println("doSomething");
}
@Override
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
if (args != null) {
for (Object arg : args) {
System.out.println(" " + arg);
}
}
return method.invoke(proxied, args);
}
}
public class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[] { Interface.class },
new DynamicProxyHandler(real)
);
consumer(proxy);
}
}