Java运行时类型信息,反射和动态代理。

Java运行时类型信息(Run-Time Type Identification)使得你可以在程序运行时发现和使用类型信息。
  ——《Java编程思想》

1.类型信息与多态

  面向对象编程中的基本目的是:让代码只操纵对基类的引用,如示例中的Animal。这里实际上是将DogCat向上转型为Animal,在之后的程序使用中完成了“匿名”。另外,Java中所有的类型转换都是在运行时进行正确性检查,这也是RTTI最基本的使用形式,即:在运行时识别一个对象的类型。

e.g.

// Animal类
public abstract class Animal {
    abstract void getName();
}

// Dog类
public class Dog extends Animal {
    @Override
    void getName() {
        System.out.println("Dog");
    }
}

// Cat类
public class Cat extends Animal {
    @Override
    void getName() {
        System.out.println("Cat");
    }
}

2.Class对象

  每个类都有一个特殊的Class对象,它包含了与类有关的信息,Class对象由类装在子系统生成。
获取类的Class实例的三种方法:

  1. 通过静态变量class获取
Class cls = String.class;
  1. 使用getClass()获取实例的类型信息
String s = "String";
Class cls = s.getClass();
  1. 通过类的完整类名获取
try {
    Class c = Class.forName("blog.Dog");
} catch (Exception e) {
    e.printStackTrace();
}

通常使用Class<? extends Animal>,通过通配符结合继承关系来限定范围。

3.instanceof

  instanceof返回一个boolean值,用于判断一个对象是不是指定类型的实例或接口的实现。而使用==可以精确判断数据类型。

4.反射

反射可以说是Java中最重要的技术之一了,整个Spring框架都大量依赖于反射技术。

反射是一种能够在程序运行时获取类信息的机制。Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethodConstructor类。反射机制的实现逻辑是:当程序通过反射与一个未知类型的对象打交道时,JVM会检查这个对象,看它属于哪个特定的类。

// 获取字段信息的方法
public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Student.class;

        // 获取父类public字段
        Field name = cls.getField("name");
        Field age = cls.getField("age");
        // 获取私有字段
        Field phone = cls.getDeclaredField("phone");

        // 获取字段名称
        String fieldName = name.getName();
        // 获取字段类型
        Class fieldType = name.getType();
        // 获取字段修饰符
        int fieldModifier = name.getModifiers();

        Person std = new Student(20, "110119120");
        int stdAge = (int) age.get(std);
        // 获取private字段的值
        phone.setAccessible(true);
        String stdPhone = (String) phone.get(std);

        // 设置字段的值
        age.set(std, 18);
        // 获取父类
        Class superCls = cls.getSuperclass();
        // 获取当前类实现的所有接口
        Class[] interfaceCls = cls.getInterfaces();
    }
}

访问方法和构造方法都是类似的,具体的方法名参考MethodConstructor类。

5.动态代理

代理是基本设计模式,它可以提供额外的操作。实际上,日志之类的可以通过代理类这种方式实现,只是代码冗余量会非常大。Proxy.newProxyInstance()可以创建动态代理,它的第一个参数是一个类加载器,在示例代码中也传入了Easy.class参数,这并不意味着要用这个类加载器去实例化接口,接口是不能实例化的,代理实际上是JVM在运行期动态创建class字节码并加载的过程,而加载类需要类加载器。

接口

public interface Easy {
    void doSomething();
    void somethingElse(String s);
}

实现类

public class SayHello implements Easy {
    @Override
    public void doSomething() {
        System.out.println("Hello");
    }

    @Override
    public void somethingElse(String s) {
        System.out.println("Hello " + s);
    }
}

代理类

public 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);
        return method.invoke(proxied, args);
    }
}

启动类

public class DynamicProxy {
    public static void main(String[] args) {
        SayHello sayHello = new SayHello();
        consumer(sayHello);

        // 创建动态代理
        Easy proxy = (Easy) Proxy.newProxyInstance(Easy.class.getClassLoader(),
                new Class[] { Easy.class },
                new DynamicProxyHandler(sayHello));
        consumer(proxy);
    }

    public static void consumer(Easy easy) {
        easy.doSomething();
        easy.somethingElse("XXX");
    }
}