反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
反射机制概述
Java 反射是 Java 被视为动态语言的关键,反射机制运行程序在执行期借助于 Reflection API 去获取任何类内部的信息,并能直接操作任意对象的内部属性及方法。在加载完类后,在堆内存的方法区就产生了一个 Class 类型的对象,这个对象包含了完整的类结构信息,可以通过这个 Class 对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为:反射。
反射常用方法
1. Class
通过反射生成 Class 对象
/**
* 返回与具有给定字符串名称的类或接口关联的Class对象,该方法会导致类被初始化
* 调用此方法等效于:Class.forName(className, true, currentLoader)
*/
public static Class<?> forName(String className)
/**
* 使用给定的类加载器,返回与具有给定字符串名称的类或接口关联的对象
* 此方法尝试查找,加载和链接类或接口
*
* 仅当initialize参数为true且先前尚未初始化时,才初始化该类
* loader用于加载类或接口。如果loader为null,则通过bootstrap类加载器加载该类
*/
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
/**
* 创建由此Class对象表示的类的新实例。通过空参数列表的构造函数实例化该类
* 如果尚未初始化该类,则将其初始化
*/
public T newInstance()
信息获取
# 打印类的全限定名
public String getName()
# 只打印简单类名
public String getSimpleName()
public ClassLoader getClassLoader()
# 获取父类的类型信息
public native Class<? super T> getSuperclass()
public Package getPackage()
# 返回该对象表示的类或接口实现的接口数组,数组顺序与声明的接口顺序相对应
public Class<?>[] getInterfaces()
public InputStream getResourceAsStream(String name)
public java.net.URL getResource(String name)
获取 Field 相关信息
/**
* 返回一个Field对象的数组,数组元素包含了此类,及其所有超类和超接口的所有public字段
* 返回的数组中的元素未排序并且没有任何特定的顺序
*/
public Field[] getFields()
public Field getField(String name)
/**
* 返回一个Field对象的数组,数组元素包含了该类声明的所有字段
* 包括public、protected、default、private,但不包括继承的字段
* 返回的数组中的元素未排序,并且没有任何特定顺序。
*/
public Field[] getDeclaredFields()
public Field getDeclaredField(String name)
获取 Method 相关信息
/**
* 获取所有的公有的方法,并且会把父类的公有方法也获取到
*/
public Method[] getMethods()
public Method getMethod(String name, Class<?>... parameterTypes)
/**
* 获取到公有和私有的方法,不管是静态还是非静态,但是获取不到父类的方法
*/
public Method[] getDeclaredMethods()
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
获取 Constructor 相关信息
public Constructor<?>[] getConstructors()
public Constructor<T> getConstructor(Class<?>... parameterTypes)
public Constructor<?>[] getDeclaredConstructors()
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
注解相关
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)
2. Constructor
/**
* 通过调用指定参数的构造函数来声明类的新实例,如果构造函数正常完成,则返回新创建并初始化的实例
*/
public T newInstance(Object ... initargs)
newInstance() 在 JDK9 中已经置为过时,使用 getDeclaredConstructor().newlnstance() 的方式。这里着重说明一下 new 与 newInstance 的区别:
new 是强类型校验,可以调用任何构造方法,在使用 new 操作的时候,这个类可以没有被加载过。
newInstance 是弱类型,只能调用无参构造方法,如果没有默认构造方法则抛出 InstantiationException 异常,如果此构造方法没有权限访问,则抛出 IllegalAccessException 异常。
3. Field
信息获取
/**
* 返回一个Class对象,该对象表示此Field对象表示的字段的声明类型
*/
public Class<?> getType()
/**
* 返回一个Type对象,该对象表示此Field对象表示的字段的声明类型
* 如果Type是参数化类型,则返回的Type对象会包含源代码中使用的real type参数(泛型信息)
*/
public Type getGenericType()
/**
* 返回描述该字段的字符串,包括其泛型类型。
* 格式为:访问修饰符+字段类型(含泛型信息)+字段所在类的全限定名+字段名称
*
* 其他修饰符的顺序如下:static -> final -> transient -> volatile
*/
public String toGenericString()
对字段属性进行 get、set 操作
/**
* 返回指定对象上此Field表示的字段的值。如果Field字段是静态的,则忽略obj参数,它可以为空
*/
public Object get(Object obj)
public boolean getBoolean(Object obj)
public byte getByte(Object obj)
public char getChar(Object obj)
public short getShort(Object obj)
public int getInt(Object obj)
public long getLong(Object obj)
public float getFloat(Object obj)
public double getDouble(Object obj)
/**
* 将指定对象上的Field字段设置为指定的新值。如果Field字段是静态的,则忽略obj参数,它可以为空
*/
public void set(Object obj, Object value)
public void setBoolean(Object obj, boolean z)
public void setByte(Object obj, byte b)
public void setChar(Object obj, char c)
public void setShort(Object obj, short s)
public void setInt(Object obj, int i)
public void setLong(Object obj, long l)
public void setFloat(Object obj, float f)
public void setDouble(Object obj, double d)
4. Method
返回值信息获取
/**
* 返回一个Class对象,表示此Method对象表示的方法的返回值类型
*/
public Class<?> getReturnType()
/**
* 返回一个Type对象,表示此Method对象表示的方法的返回值类型
* 如果返回类型是参数化类型,则返回的Type对象包含源代码中使用的实际类型参数(泛型信息)
*/
public Type getGenericReturnType()
/**
* 返回描述该方法的字符串,包括其泛型类型。
* 格式为:访问修饰符+方法返回类型(含泛型)+方法所在类的全限定名+方法名+参数信息(含泛型)+抛出的异常类型
*
* 其他修饰符顺序如下:abstract -> default -> static -> final -> sync -> native -> strictfp
*/
public String toGenericString()
参数信息获取
/**
* 返回Class对象数组,数组按声明顺序表示该方法的参数类型
*/
public Class<?>[] getParameterTypes()
/**
* 返回Type对象的数组,数组按声明顺序表示该方法的参数类型
* 如果参数类型是参数化类型,返回的Type对象会包含源代码中使用的实际类型参数(泛型信息)
*/
public Type[] getGenericParameterTypes()
异常信息获取
public Class<?>[] getExceptionTypes()
public Type[] getGenericExceptionTypes()
方法调用
/**
* 在具有指定参数的指定对象上调用此Method,各个参数会进行自动匹配
* 如果基础方法是静态的,则忽略指定的obj参数,它可以为空
* 如果基础方法所需的参数数量为0,则args数组的长度可以为0或为null
*/
public Object invoke(Object obj, Object... args)
当一个类中有多个重载方法,在执行 invoke 调用时需要注意如何确定要反射调用的是哪个重载方法。比如下面示例中有两个叫 age 的方法,入参分别是基本类型 int 和包装类型 Integer。如果不通过反射调用,走哪个重载方法很清晰,比如传入 36 走 int 参数的重载方法,传入 Integer.valueOf(“36”) 走 Integer 重载。
public class ReflectionIssueApplication {
private void age(int age) {
log.info("int age = {}", age);
}
private void age(Integer age) {
log.info("Integer age = {}", age);
}
}
但使用反射时的误区是,认为反射调用方法还是根据入参确定方法重载。比如,使用 getDeclaredMethod 来获取 age 方法,然后传入 Integer.valueOf(“36”):
getClass().getDeclaredMethod("age", Integer.TYPE).invoke(this, Integer.valueOf("36"));
// 输出结果:
int age = 36
其实,要通过反射进行方法调用,第一步就是通过方法签名来确定方法。具体到这个案例,getDeclaredMethod 传入的参数类型 Integer.TYPE 代表的是 int,所以实际执行方法时无论传的是包装类型还是基本类型,都会调用 int 入参的 age 方法。如果改为 Integer.class 则执行的参数类型就是包装类型的 Integer。这时,无论传入的是 Integer.valueOf(“36”) 还是基本类型的 36,都会调用 Integer 为入参的 age 方法:
getClass().getDeclaredMethod("age", Integer.class).invoke(this, Integer.valueOf("36"));
getClass().getDeclaredMethod("age", Integer.class).invoke(this, 36);
// 输出结果:
Integer age = 36
Integer age = 36
现在我们确定了,反射调用方法是以反射获取方法时传入的方法名称和参数类型来确定调用方法的。
5. 继承关系
- AnnotatedElement:注解相关操作
- AccessibleObject:权限相关操作
关于反射需要特意提一下,就是反射提供的 AccessibleObject.setAccessible(boolean flag)。它的子类也大都重写了这个方法,这里的所谓 accessible 可以理解成修饰成员的 public、protected、private,这意味着我们可以在运行时修改成员访问限制!
使用 setAccessible 的典型场景就是绕过 API 访问控制。在开发时可能被迫要调用内部 API 去做些事情,比如,自定义的高性能 NIO 框架需要显式地释放 DirectBuffer,使用反射绕开限制是一种常见办法。
反射操作泛型
Java 采用泛型擦除机制来引入泛型,Java 中的泛型仅仅是给编译器使用的,为了确保数据的安全性和免去强制类型转换的问题,一旦编译完成后,所有泛型有关的类型会全部被擦除。为了通过反射操作这些类型,Java新增了 ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型。
ParameterizedType:描述泛型类或接口类型,如 Comparable<? super T>
// 获得这个参数化类型的原始类型
Type getRawType();
// 获得这个参数化类型声明的类型参数
Type[] getActualTypeArguments();
// 如果是内部类型,则返回其内部类型,如果是一个顶级类型则返回null
Type getOwnerType();
GenericArrayType:描述元素类型是参数化类型或者类型变量的数组类型,如 T[]
// 获得这个数组类型声明的泛型元素类型
Type getGenericComponentType();
TypeVariable:描述类型变量,是各种类型变量的公共父接口,如 T extends Comparable<? super T>
// 获得这个类型变量的名字
String getName();
// 获得这个类型变量的子类限定,无限定则返回长度为0的数组
Type[] getBounds();
WildcardType:描述通配符类型的表达式,如 ? super T
// 获得这个类型变量的子类(extends)限定,无限定则返回长度为0的数组
Type[] getUpperBounds();
// 获得这个类型变量的超类(super)限定,无限定则返回长度为0的数组
Type[] getLowerBounds();
1. 使用示例
下面通过代码来获取方法上的泛型,包括参数泛型,以及返回值泛型:
public class GenericDemo extends GenericParent<Double> implements Comparable<String>, Serializable {
public Map<String, Bird<? extends String>> test(List<Bird<? extends Comparable<? super String>>> list) {
return null;
}
private static void printType(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// rawType
Type rawType = parameterizedType.getRawType();
System.out.println("ParameterizedType RawType: " + rawType);
// actualTypeArguments
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("ParameterizedType ActualTypeArgument: " + actualTypeArgument);
printType(actualTypeArgument);
}
// ownerType
Type ownerType = parameterizedType.getOwnerType();
System.out.println("ParameterizedType OwnerType: " + ownerType);
}
else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
// super限定类型
Type[] lowerType = wildcardType.getLowerBounds();
for (Type lower : lowerType) {
System.out.println("WildcardType LowerBound: " + lower);
printType(lower);
}
// extends限定类型
Type[] upperType = wildcardType.getUpperBounds();
for (Type upper : upperType) {
System.out.println("WildcardType UpperBound: " + upper);
printType(upper);
}
}
}
public static void main(String[] args) throws Exception {
// 类型信息
Type superType = GenericDemo.class.getGenericSuperclass();
System.out.println("-----------------------------superType-----------------------------");
System.out.println(superType);
printType(superType);
System.out.println("-------------------------------------------------------------------");
System.out.println("-----------------------------interfaceType-------------------------");
Type[] interfaceType = GenericDemo.class.getGenericInterfaces();
for (Type type : interfaceType) {
System.out.println(type);
printType(type);
}
System.out.println("-------------------------------------------------------------------");
// 参数泛型列表
System.out.println("-----------------------------methodParameter-----------------------");
Method method = GenericDemo.class.getMethod("test", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
printType(genericParameterType);
}
System.out.println("-------------------------------------------------------------------");
// 返回值泛型列表
System.out.println("-----------------------------returnParameter-----------------------");
Type returnGenericParameterTypes = method.getGenericReturnType();
System.out.println(returnGenericParameterTypes);
printType(returnGenericParameterTypes);
System.out.println("-------------------------------------------------------------------");
}
@Override
public int compareTo(String o) {
return 0;
}
public static class Bird<T> implements Comparable<T> {
@Override
public int compareTo(T o) {
return 0;
}
}
}
输出结果如下:
-----------------------------superType-----------------------------
org.xl.java.type.GenericParent<java.lang.Double>
ParameterizedType RawType: class org.xl.java.type.GenericParent
ParameterizedType ActualTypeArgument: class java.lang.Double
ParameterizedType OwnerType: null
-------------------------------------------------------------------
-----------------------------interfaceType-------------------------
java.lang.Comparable<java.lang.String>
ParameterizedType RawType: interface java.lang.Comparable
ParameterizedType ActualTypeArgument: class java.lang.String
ParameterizedType OwnerType: null
interface java.io.Serializable
-------------------------------------------------------------------
-----------------------------methodParameter-----------------------
java.util.List<org.xl.java.type.GenericDemo$Bird<? extends java.lang.Comparable<? super java.lang.String>>>
ParameterizedType RawType: interface java.util.List
ParameterizedType ActualTypeArgument: org.xl.java.type.GenericDemo$Bird<? extends java.lang.Comparable<? super java.lang.String>>
ParameterizedType RawType: class org.xl.java.type.GenericDemo$Bird
ParameterizedType ActualTypeArgument: ? extends java.lang.Comparable<? super java.lang.String>
WildcardType UpperBound: java.lang.Comparable<? super java.lang.String>
ParameterizedType RawType: interface java.lang.Comparable
ParameterizedType ActualTypeArgument: ? super java.lang.String
WildcardType LowerBound: class java.lang.String
WildcardType UpperBound: class java.lang.Object
ParameterizedType OwnerType: null
ParameterizedType OwnerType: class org.xl.java.type.GenericDemo
ParameterizedType OwnerType: null
-------------------------------------------------------------------
-----------------------------returnParameter-----------------------
java.util.Map<java.lang.String, org.xl.java.type.GenericDemo$Bird<? extends java.lang.String>>
ParameterizedType RawType: interface java.util.Map
ParameterizedType ActualTypeArgument: class java.lang.String
ParameterizedType ActualTypeArgument: org.xl.java.type.GenericDemo$Bird<? extends java.lang.String>
ParameterizedType RawType: class org.xl.java.type.GenericDemo$Bird
ParameterizedType ActualTypeArgument: ? extends java.lang.String
WildcardType UpperBound: class java.lang.String
ParameterizedType OwnerType: class org.xl.java.type.GenericDemo
ParameterizedType OwnerType: null
-------------------------------------------------------------------
2. 泛型经类型擦除多出桥接方法的坑
假设:父类有一个泛型占位符 T;有一个 AtomicInteger 计数器,用来记录 value 字段更新的次数,其中 value 字段是泛型 T 类型的,setValue 方法每次为 value 赋值时对计数器进行 +1 操作。这里重写了 toString 方法用来输出 value 字段的值和计数器的值:
public class Parent<T> {
//用于记录value更新的次数,模拟日志记录的逻辑
AtomicInteger updateCount = new AtomicInteger();
private T value;
//重写toString,输出值和值更新次数
@Override
public String toString() {
return String.format("value: %s updateCount: %d", value, updateCount.get());
}
public void setValue(T value) {
this.value = value;
updateCount.incrementAndGet();
}
}
子类 Child 继承了 Parent 父类,定义了一个参数为 String 的 setValue 方法,通过 super.setValue 调用父类方法来实现日志记录:
public class Child extends Parent<String> {
@Override
public void setValue(String value) {
System.out.println("Child.setValue called");
super.setValue(value);
}
}
此时,如果我们想对子类 Child 的 setValue 方法进行反射调用,具体操作如下。实例化 Child 类型后,通过 getClass().getMethods 方法获得所有的方法,然后按照方法名过滤出 setValue 方法进行调用,传入字符串 test 作为参数:
Child child = new Child();
Arrays.stream(child.getClass().getMethods())
.filter(method -> method.getName().equals("setValue"))
.forEach(method -> {
try {
method.invoke(child, "test");
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(child.toString());
我们知道,Java 的泛型类型在编译后擦除为 Object。虽然子类指定了父类泛型 T 类型是 String,但编译后 T 会被擦除成为 Object,所以父类 setValue 方法的入参是 Object,value 也是 Object。如果子类 Child 的 setValue 方法要覆盖父类的 setValue 方法,那入参也必须是 Object。
但是子类已经指定了泛型的类型为 String,因此子类的 setValue 方法入参为 String。为了解决这个类型不匹配的问题,编译器在编译时会为我们生成一个所谓的 bridge 桥接方法,你可以使用 javap 命令来反编译编译后的 Child 类的 class 字节码:
class Child extends Parent<java.lang.String> {
Child();
Code:
0: aload_0
1: invokespecial #1 // Method Parent."<init>":()V
4: return
public void setValue(java.lang.String);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Child.setValue called
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: aload_1
10: invokespecial #5 // Method Parent.setValue:(Ljava/lang/Object;)V
13: return
public void setValue(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #6 // class java/lang/String
5: invokevirtual #7 // Method setValue:(Ljava/lang/String;)V
8: return
}
可以看到,入参为 Object 的 setValue 方法在内部调用了入参为 String 的 setValue 方法(第 23 行),也就是代码里实现的那个方法。如果编译器没有帮我们实现这个桥接方法,那么 Child 子类重写的是父类经过泛型类型擦除后、入参是 Object 的 setValue 方法。这两个方法的参数,一个是 String 一个是 Object,这明显不符合 Java 的语义。
知道这个问题之后,修改方式就明朗了,可以使用 method 的 isBridge 方法,来判断方法是不是桥接方法。通过 getDeclaredMethods 方法获取到所有方法后,必须同时根据方法名 setValue 和非 isBridge 两个条件过滤,才能实现唯一过滤。修复代码如下:
Arrays.stream(child.getClass().getDeclaredMethods())
.filter(method -> method.getName().equals("setValue") && !method.isBridge())
.findFirst().ifPresent(method -> {
try {
method.invoke(child, "test");
} catch (Exception e) {
e.printStackTrace();
}
});
这样就可以得到正确输出了:
Child.setValue called
value: test updateCount: 1
获取对象 Class 实例
反射都是针对类的 Class 实例来进行操作的,那如何获取一个类的 Class 实例呢?
- 若已知具体的类,可通过类的 class 属性获取,该方法最为安全可靠,程序性能最高
- 已知某个类的实例,调用该实例的 getClass() 方法也可以获取 Class 对象
- 已经一个类的全限定名,并且该类在类路径下,可以通过 Class 类的静态方法 forName() 获取
- 内置的原始数据类型可以直接通过 类名.Type 获取
- 还可以利用 ClassLoader
1. Class.forName 和 ClassLoader 区别
Class.forName() 和 ClassLoader 都可以对类进行加载。ClassLoader 就是遵循双亲委派模型最终调用启动类加载器的类加载器,Class.forName() 方法实际上也是调用的 ClassLoader 来实现的。
下面是 Class.forName(String className) 方法的源码:
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
最后是调用了 forName0 这个方法,该方法中的第二个参数表示是否对加载的类进行初始化,设置为 true 时会对类进行初始化,初始化过程会执行类中的静态代码块以及对静态变量的赋值等操作。此外,我们也可以通过其重载方法 Class.forName(String name, boolean initialize,ClassLoader loader) 来手动选择在加载类的时候是否要对类进行初始化。
下面举例说明,但要注意执行 main 方法的类是一个单独的类,因为执行 main 方法时会先加载 main 方法所在的类,对测试结果会有影响:
public class ClassForName {
//静态代码块
static {
System.out.println("执行了静态代码块");
}
//静态变量
private static String staticFiled = staticMethod();
//赋值静态变量的静态方法
public static String staticMethod(){
System.out.println("执行了静态方法");
return "给静态字段赋值了";
}
}
使用 Class.forName() 进行测试:
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.demo.ClassForName");
System.out.println("#########-------------结束符------------##########");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 输出结果:
执行了静态代码块
执行了静态方法
#########-------------结束符------------##########
使用 ClassLoader 进行测试:
public class Test {
public static void main(String[] args) {
try {
ClassLoader.getSystemClassLoader().loadClass("com.demo.ClassForName");
System.out.println("#########-------------结束符------------##########");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 输出结果:
#########-------------结束符------------##########
根据运行结果可以得出结论:Class.forName 加载类时会对类进行初始化操作,而 ClassLoader 的 loadClass 并没有对类进行初始化,只是把类加载到了虚拟机中。