[笔记] Java 类型信息

By | 6月 18, 2018

在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为反射机制。

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);
    }
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注