# 1. 反射

JVM在第一次读取到一种class类型时,将其加载进内存。

每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。注意:这里的Class类型是一个名叫Classclass。它长这样:

public final class Class {
    private Class() {}
}

String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。

小结:

  1. JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;
  2. 获取一个class对应的Class实例后,就可以获取该class的所有信息;
  3. 通过Class实例获取class信息的方法称为反射(Reflection);
  4. JVM总是动态加载class,可以在运行期根据条件来控制加载class。

方法一:直接通过一个class的静态变量class获取:

Class cls = String.class;

方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

String s = "Hello";
Class cls = s.getClass();

方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

Class cls = Class.forName("java.lang.String");

# 2. 反射访问字段

  1. Java的反射API提供的Field类封装了字段的所有信息:
  2. 通过Class实例的方法可以获取Field实例:getField()getFields()getDeclaredField()getDeclaredFields()
  3. 通过Field实例可以获取字段信息:getName()getType()getModifiers()
  4. 通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
  5. 通过反射读写字段是一种非常规方法,它会破坏对象的封装。
Class stdClass = Student.class;
// 获取public字段"score":
System.out.println(stdClass.getField("score"));
// 获取继承的public字段"name":
System.out.println(stdClass.getField("name"));
// 获取private字段"grade":
System.out.println(stdClass.getDeclaredField("grade"));

# 3. 反射调用方法

Java的反射API提供的Method对象封装了方法的所有信息:

  1. 通过Class实例的方法可以获取Method实例:getMethod()getMethods()getDeclaredMethod()getDeclaredMethods()
  2. 通过Method实例可以获取方法信息:getName()getReturnType()getParameterTypes()getModifiers()
  3. 通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)
  4. 通过设置setAccessible(true)来访问非public方法;
  5. 通过反射调用方法时,仍然遵循多态原则。

调用静态方法:

// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用结果:
System.out.println(n);

调用非 public 方法:

Person p = new Person();
Method m = p.getClass().getDeclaredMethod("setName", String.class);
m.setAccessible(true);
m.invoke(p, "Bob");
System.out.println(p.name);

# 4. 反射调用构造方法

Constructor对象封装了构造方法的所有信息;

  1. 通过Class实例的方法可以获取Constructor实例:getConstructor()getConstructors()getDeclaredConstructor()getDeclaredConstructors()
  2. 通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。
// 获取构造方法Integer(int):
Constructor cons1 = Integer.class.getConstructor(int.class);
// 调用构造方法:
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1);

// 获取构造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);

# 5. 反射获取继承关系

通过Class对象可以获取继承关系:

  • Class getSuperclass():获取父类类型;
  • Class[] getInterfaces():获取当前类实现的所有接口。

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

Class s = Integer.class;
Class n = s.getSuperclass();
Class[] is = s.getInterfaces();
for (Class i : is) {
    System.out.println(i);
}
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

# 6. 动态代理

先定义了接口 Hello,但是我们并不去编写实现类,而是直接通过 JDK 提供的一个Proxy.newProxyInstance() 创建了一个 Hello 接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

一个最简单的动态代理实现如下:

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;

  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:

    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

动态代理实际上是JDK在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法。