# 反射
# 1. 反射的概念
『反射』和注解、动态代理、类加载器等被称为 Java 的动态特性。利用这些特性,可以优雅地实现一些灵活通用的功能,它们经常用于各种框架、库和系统程序中。例如:
- jackson 库利用反射和注解实现了通用的序列化机制。
- Spring MVC 框架在处理 Web 请求时,利用反射和注解,能方便地将用户的请求参数和内容转换为 Java 对象、将 Java 对象转变为响应内容。
- 面向切面编程(AOP)将编程中通用关注点(如日志记录、安全检查等)与业务的主体逻辑相分离,减少冗余代码,提高程序的可维护性。AOP 需要依赖包括反射在内的上述 Java 动态特性。
- 在单元测试中,JUnit 和 TestNG 库都使用到了反射。
如果编写的架构足够灵活,在运行时之前都不知道要处理什么代码,那么通常都需要使用反射。
在一般操作数据的时候,我们都是知道并且依赖于数据类型的,比如:
- 根据类型使用 new 创建对象;
- 根据类型定义变量,类型可能是基本类型、类、接口或数组。
- 将特定类型的对象传递给方法。
- 根据类型访问对象的属性,调用对象的方法。
另外,编译器也是根据类型进行代码的检查编译的。
但是,反射不一样。
它是在运行时,而非编译时,动态获取类型的信息(比如,接口信息、成员信息、方法信息、构造方法信息等),根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等。
反射的入口是名为 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));
← Java 集合底层原理剖析 代理模式 →