# 反射

# 1. 反射的概念

反射』和注解、动态代理、类加载器等被称为 Java 的动态特性。利用这些特性,可以优雅地实现一些灵活通用的功能,它们经常用于各种框架、库和系统程序中。例如:

  • jackson 库利用反射和注解实现了通用的序列化机制。
  • Spring MVC 框架在处理 Web 请求时,利用反射和注解,能方便地将用户的请求参数和内容转换为 Java 对象、将 Java 对象转变为响应内容。
  • 面向切面编程(AOP)将编程中通用关注点(如日志记录、安全检查等)与业务的主体逻辑相分离,减少冗余代码,提高程序的可维护性。AOP 需要依赖包括反射在内的上述 Java 动态特性。
  • 在单元测试中,JUnit 和 TestNG 库都使用到了反射。

如果编写的架构足够灵活,在运行时之前都不知道要处理什么代码,那么通常都需要使用反射。

在一般操作数据的时候,我们都是知道并且依赖于数据类型的,比如:

  1. 根据类型使用 new 创建对象;
  2. 根据类型定义变量,类型可能是基本类型、类、接口或数组。
  3. 将特定类型的对象传递给方法。
  4. 根据类型访问对象的属性,调用对象的方法。

另外,编译器也是根据类型进行代码的检查编译的。

但是,反射不一样。

它是在运行时,而非编译时,动态获取类型的信息(比如,接口信息、成员信息、方法信息、构造方法信息等),根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等。

反射的入口是名为 Class 的类。注意,是首字母大写的 Class,而不是全小写的关键字 class 。

# 2. 获得类的 Class 信息

Class 是反射的基石。

java.lang.Class 类的实例表示一种 Java 数据类型,而且包含所表示类型的元数据,它是在运行中 Java 进程里表示实时类型的方法。

每个已加载的类在内存中都有一个 Class 对象,每个对象都有指向它所属 Class 对象的引用。

# 2.1 获得 Class 对象

所有类的根父类 Object 有一个方法,可以获得对象的 Class 对象:

public final native Class<?> getClass()

Class 是一个泛型类,有一个类型参数,getClass 方法并不知道具体的类型,所以返回 Class<?> 。

获取 Class 对象不一定需要实例对象,如果在写程序时就知道类名,可以使用 <类名>.class 获取 Class 对象。比如:

Class<Date> clazz = Date.class;

接口也有 Class 对象,且这种方式对于接口也适用:

Class<Comparable> clazz = Comparable.class;

基本类型没有 getClass 方法,但也有对应的 Class 对象,类型参数未对应的包装类型:

Class<Integer> intClazz = int.class;
Class<Double> doubleClazz = double.class;

void 作为特殊的返回类型,也有对应的 Class:

Class<Void> voidClazz = void.class;

对于数组,每种类型都有对应数组类型的 Class 对象,需要注意的是,不同类型的数组它们的 Class 对象并不相同:

String[] strArr = new String[10];
int[] oneDimArr = new int[10];
int[][] towDimArr = new int[10][20];

Class<? extends String[]> strArrClazz = strArr.getClass();
Class<? extends int[]> oneDimArrClazz = oneDimArr.getClass();
Class<? extends int[][]> towDimArrClazz = twoDimArr.getClass();

Class 有一个 forName 静态方法,可以根据类名直接加载 .class 文件,获得 Class 对象:

try {
    Class<?> clazz = Class.forName("java.util.HashMap");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

注意,forName 可能抛出异常 ClassNotFoundException 。

有了 Class 对象后,我们就可以了解到关于类型的很多信息,并基于这些信息采取一些行动。

# 2.2 名称信息

Class 有如下方法,可以获取与『名称』有关的信息:

public String getName()         // Java 内部使用的真正的名字
public String getSimpleName()   // 不带包信息
public String getCanonicalName()// 相较于 getName 而言,它返回的名字对「人类」来说更友好。
public Package getPackage()     // 返回包信息

它们的不同如下表:

Class 对象 getName getSimpleName getCanonicalName getPackage
int.class int int int null
int[].class [I int[] int[] null
int[][].class [[I int[][] int[][] null
String.class java.lang.String String java.lang.String java.lang
String[].class [Ljava.lang.String; String[] java.lang.String[] null
HashMap.class java.util.HashMap HashMap java.util.HashMap java.util
Map.Entry.class java.util.Map$Entry Entry java.util.Map.Entry java.util

对于最根本、最核心的 getName 方法需要说明的是:

  • 对于数组类型,它在返回值中使用前缀 [ 表示数组,有几个 [ 表示是几维数组;
  • 在描述数组中的元素的类型时,它在返回值中使用一个字符表示。例如,I 表示 int,L 表示类或接口,其它类型与字符的对应关系为:boolean(Z)、byte(B)、char(C)、double(D)、float(F)、long(J)、short(S);
  • 对于引用类型的数组,注意最后有一个分号 ;

# 3. 获得类的 Method 信息

类中定义的静态和实例方法都被称为『方法』,用类 java.lang.reflect.Member 表示。Class 有如下相关方法:

/**
 * 返回所有的 public 方法,包括其父类的。
 * 如果没有,则返回空数组。
 */
public Method[] getMethods()

/**
 * 返回本类自己所声明的所有方法,包括非 public 的,因此不包括继承自父类的方法
 *
 */
public Method[] getDeclaredMethods()

/**
 * 返回本类中指定名称和参数的 public 方法,包括继承自父类的
 * 如果没有,则抛出异常 NoSuchMethodException
 */
public Method getMethod(String name, Class<?>... parameterTypes)

/**
 * 返回本类自己声明的指定名称和参数类型的方法,因此不包括继承自父类的
 * 如果没有,则抛出异常 NoSuchMethodException
 */
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

# 3.1 基本方法

通过 Method 可以获取方法的信息,也可以通过 Method 调用对象的方法,基本方法有:

// 获取方法的名称
public String getName()

// flag 设为 true 表示忽略 Java 的访问检查机制,以允许调用非 public 的方法。
public void setAccessible(boolean flag)

// 在指定对象 obj 上调用 Method 代表的方法,传递参数列表为 args 
public Object invoke(Object obj, Object... args) throws
IllegalAccessException, Illegal-ArgumentException, InvocationTargetException

对 invoke 方法,如果 Method 为静态方法,obj 被忽略,可以为 null,args 可以为 null,也可以为一个空的数组,方法调用的返回值被包装为 Object 返回,如果实际方法调用抛出异常,异常被包装为 InvocationTargetException 重新抛出,可以通过 Exception#getCause() 方法得到原异常。

Method 还有很多方法,可以获取其修饰符、参数、返回值、注解等信息,具体就不列举了。

# 3.1 创建对象和构造方法

Class 有一个方法,可以用它来创建对象:

public T newInstance() throws InstantiationException, IllegalAccessException

它会调用类的默认构造方法(即无参 public 构造方法),如果类没有该构造方法,会抛出异常 InstantiationException 。

Class#newInstance() 方法只能使用默认构造方法。Class 还有一些方法,可以获取所有的构造方法:

// 获取所有 public 构造方法,返回值可能长度为 0 的空数组。
public Constructor<?>[] getConstructors()

// 获取所有的构造方法,包括非 public 的
public Constructor<?>[] getDeclaredConstructors()

// 获取指定参数类型的 public 构造方法,没找到抛出异常 NoSuchMethodException
public Constructor<T> getConstructor(Class<?>... parameterTypes)

// 获取指定参数类型的构造方法,包括非 public 的。没有找到,则抛出异常 NoSuchMethodException
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

Constructor 表示构造方法,通过它可以创建对象,方法为:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
                IllegalArgumentException, InvocationTargetException

例如:

Constructor<StringBuilder> contructor = 
        StringBuilder.class.getConstructor(new Class[]{int.class});
StringBuilder sb = contructor.newInstance(100);

除了创建对象,Constructor 还有很多方法,可以获取关于构造方法的很多信息,包括参数、修饰符、注解等,具体就不列举了。

# 4. 获得类的 Field 信息

类中定义的静态和实例变量都被称为『字段』,用类 Field 表示,位于包 java.lang.reflect 下(反射相关的类都在这个包下)

Class 有 4 个获取字段信息的方法:

/**
 * 返回所有的 public 字段,包括继承自父类的。
 * 如果没有字段,返回空数组。
 */
public Field[] getFields()

/** 
 * 返回本类自己声明的所有字段,包括非 public 的。因此不包括继承自父类的。
 */
public field[] getDeclaredFields()

/* 
 * 返回本类中指定名称的 public 字段,包括继承自父类的。
 * 如果没有,则抛出异常 NoSuchFieldException
 */
public Field getField(String)

/** 
 * 返回本类自己声明的指定名称的字段。因此不包含继承自父类的。
 * 如果没有,则抛出异常 NoSuchFieldException
 */
public Field getDeclaredField(String)

Field 也有很多方法,可以获取字段的信息,也可以通过 Field 访问和操作指定对象中该字段的值,基本方法有:

// 获取字段的名称
public String getName()

// 判断当前程序是否有该字段的访问权限
public boolean isAccessiable()

// flag 设置为 true 表示忽略 Java 的访问检查机制,以允许读写非 public 的字段。
public void setAccessible(boolean flag)

// 获取指定对象 obj 中该字段的值
public Object get(Object obj)

// 将指定对象 obj 中该字段的值设为 value
public void set(Object obj, Object value)

在 get/set 方法中,对于静态变量,obj 被忽略,可以为 null,如果字段值为基本类型,get/set 会自动在基本类型与对应包装类型之间进行转换;对于 private 字段,直接使用 get/set 会抛出非法访问异常 IllegalAccessException,应该先调用 Field#setAccessible(true) 以关闭 java 的检查机制。

除了以上方法,Field 还有很多其它方法,比如:

// 返回字段的修饰符
public int getModifiers()

// 返回字段的类型
public Class<?> getType()

// 以基本类型操作字段
public void setBoolean(Object obj, boolean z)
public boolean getBoolean(Object, obj)
public void setDouble(Object, double d)
public double getDouble(Object obj)

// 查询字段的注解信息,后续结合注解使用
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
public Annotation[] getDeclaredAnnotation()

Field#getModifiers 返回一个 int ,可以通过 Modifier 类的静态方法进行解读。

Field f = ...;
int mod = f.getModifiers();

System.out.println(Modifier.toString(mod));
System.out.println("  isPublic: " + Modifier.isPublic(mod));
System.out.println("  isStatic: " + Modifier.isStatic(mod));
System.out.println("   isFinal: " + Modifier.isFinal(mod));
System.out.println("isVolatile: " + Modifier.isVolatile(mod));