前言
本章主要介绍 Java 反射的定义,反射相关类,以及反射 API 的使用。
推荐资料:韩顺平 Java 反射专题
版本约定
- JDK Version:11.0.12
 - Java SE API Documentation:https://docs.oracle.com/en/java/javase/11/docs/api/index.html
正文
反射的定义
 
Java 反射机制允许程序在执行期间借助反射 API 获取任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性和方法。反射在设计模式和框架底层都会用到。
在加载完某个类之后,在堆中就会产生一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了某个类的完整结构信息。通过这个对象就可以得到类的结构。这个对象就像一面镜子,透过这个镜子就可以看到类的结构,所以,形象的称之为反射。
关于反射的用途和缺点,参考官方文档:https://docs.oracle.com/javase/tutorial/reflect/index.html
反射机制原理图

Java 程序在计算机中有三个阶段,分别是编译阶段、加载阶段、运行阶段,通常我们更加熟悉运行阶段。
比如我们编写了一个 Cat.java 类,它的内容如下所示:
public class Cat {private String name;public Cat() {}public void hi() {}}
通过 javac 命令完成 Cat 类的编译,生成一个 Cat.class 的字节码文件。
当我们运行Cat cat = new Cat()代码的时候,类加载器(ClassLoader)会将 Cat 类的字节码文件加载到堆内存中,生成一个 Class 类对象,这个 Class 类对象包含了 Cat 类的完整结构信息(成员变量、构造器、成员方法等等)。类加载器的这一动作就提现了 Java 的反射机制。
当 Cat 类加载完后,就会生成一个 Cat 对象,这个对象也存在在堆内存中,并且该对象知道它是属于那个 Class 类对象的,可以简单理解成 Cat 对象和 Class 类对象之间存在映射关系,所以,我们通过cat.getClass()方法可以得到它的 Class 类对象。
上图可以分为两部分,左边部分 JVM 完成类的加载工作(字节码编译不属于 JVM 范畴),右边部分属于应用,我们在拿到某个类的 Class 类对象之后,可以创建这个类的对象,调用这个类的方法,操作这个类的属性等。
反射主要类
反射相关的主要类有如下几个:
- java.lang.Class:代表一个类;
 - java.lang.reflect.Method:代表类的成员方法;
 - java.lang.reflect.Field:代表类的成员变量;
 - java.lang.reflect.Constructor:代表类的构造方法。
Class 类
 
Class 类是我们使用反射功能的入口,一切反射的使用都要从获得一个 Class 类对象开始。
引用一段《Java 核心技术 卷1 基础知识》中描述 Class 类的内容:
在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
可以通过专门的 Java 类访问这些信息,保存这些信息的类被称为 Class。
Class 类具有如下几种特性:
- Class 类和其他类是一样的,都继承了 Object 类,只是这个类的功能有一些特殊,它的类图如下所示。
 

- Class 类对象不是 new 出来的,而是 JVM 创建的。通过上面的反射机制原理图,可以知道 Class 类对象是通过类加载器(ClassLoader)生成的。
 

Debug 上面的代码,Force Step Into,可以进入到 ClassLoader.loadClass 方法中。
对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次。关于这点,也好解释,上面的代码,如果我们继续对反射方式 Debug 的时候,就不会再次调用 ClassLoader.loadCalss 方法了,因为传统方式已经加载了 Cat 类。另外,通过下面的代码也可以看出来。
public static void main(String[] args) throws ClassNotFoundException {Class<?> cat1 = Class.forName("test15.Cat");Class<?> cat2 = Class.forName("test15.Cat");System.out.println("cat1.hashCode(): " + cat1.hashCode());System.out.println("cat2.hashCode(): " + cat2.hashCode());}
运行程序,输出:
cat1.hashCode(): 403424356cat2.hashCode(): 403424356
两个对象的 hashCode 一样。
每个类的对象都知道自己是由哪个 Class 类对象生成的。
public static void main(String[] args) {Cat cat = new Cat();Class clz = cat.getClass();}
比如可以通过 Cat 类对象获取它对应的 Class 类对象。
通过 Class 类对象可以得到一个类的完整结构。
- Class 类对象是存放在堆中的。
 - 类的字节码二进制数据,存放在方法区。
 
获取 Class 类对象的几种方式:
- 已知某个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取 Class 类对象,可能抛出 ClassNotFoundException;
Class cls1 = Class.forName("test1.Cat");
 - 已知具体的类,通过类的 class 获取 Class 类对象,该方式最为安全可靠,且程序性能最高;
Class cls2 = Cat.class;
 - 已知某个类的实例,调用该实例的 getClass() 方法获取 Class 类对象;
Class cls3 = 对象.getClass();
 - 通过类加载器获取 Class 类对象;
ClassLoader cl = 对象.getClass().getClassLoader();Class cls4 = cl.loadClass("类的全类名");
 - 基本数据类型(int,char,boolean,float,double,byte,long,short)和数组类型按如下方式得到 Class 类对象;
Class cls5_1 = 基本数据类型.class;Class cls5_2 = 数组类型.class;
 - 基本数据类型对应的包装类,可通过 TYPE 得到 Class 类对象;
Class cls6 = 包装类.TYPE;
常用的就前面三种方式。public static void main(String[] args) throws ClassNotFoundException {Class cls1 = Class.forName("test1.Cat");Class cls2 = Cat.class;Class cls3 = new Cat().getClass();Class cls4 = Test9.class.getClassLoader().loadClass("test1.Cat");Class cls5_1 = int.class;Class cls5_2 = int[].class;Class cls6 = Integer.TYPE;}
 
Class 类的方法比较多,常用的方法就这么几种:
- 获取 Class 类对象:forName
 - 获得实体的名称:getName
 - 动态地创建一个类的实例:newInstance
 - 获得实体的成员变量(Field)相关的方法:getFields,getField,getDeclaredFields,getDeclaredField
 - 获得实体的成员方法(Method)相关的方法:getMethods,getMethod,getDeclaredMethods,getDeclaredMethod
 - 获得实体的构造函数(Constructor)相关的方法:getConstructors,getDeclaredConstructors,getConstructor,getDeclaredConstructor
 
以上方法的具体使用以及其他方法的介绍参考:Class 类
其他类
反射的其他主要类 Field,Method 以及 Constructor 都是通过 Class 类获取的,我们在案例中介绍。
案例
利用反射分析类的能力
在 java.lang.reflect 包中有三个类 Field、Method 和 Constructor 分别用于描述类的域、方法和构造器。
Class 类中的 getFields、getMethods 和 getConstructors 方法将分别返回类提供的 public 域、方法和构造器数组,其中包括超类的共有成员。
Class 类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors 方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
Field、Method 和 Constructor 都提供了哪些常见的方法用来分析类的能力?
- Field、 Method 和 Constructor 都有一个叫做 getName 的方法, 用来返回项目的名称;
 - Field 类有一个 getType 方法, 用来返回描述域所属类型的 Class 对象;
 - Method 和 Constructor 类有能够获取方法参数类型的方法,Method 类还有一个可以获取方法返回类型的方法;
 - 这三个类还有一个叫做 getModifiers 的方法,它将返回一个整型数值,用不同的位开关描述 public 和 static 这样的修饰符使用状况。
 - 可以利用 java.lang.reflect 包中的 Modifier 类的静态方法分析 getModifiers 方法返回的整型数值。例如, 可以使用 Modifier 类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final。
 
接下来,我们利用反射,打印出一个类的全部信息(域、方法和构造器)。
public class ReflectionTest {public static void main(String[] args) throws ClassNotFoundException {String name = "java.lang.Double";Class clazz = Class.forName(name);Class superClazz = clazz.getSuperclass();String modifiers = Modifier.toString(clazz.getModifiers());if (modifiers.length() > 0) {System.out.print(modifiers + " ");}System.out.print("class " + name);if (superClazz != null && superClazz != Object.class) {System.out.print(" extends " + superClazz.getName());}System.out.print("\n{\n");printConstructors(clazz);System.out.println();printMethods(clazz);System.out.println();printFields(clazz);System.out.println("}");}/*** Prints all constructors of a class** @param clazz*/public static void printConstructors(Class clazz) {Constructor[] constructors = clazz.getDeclaredConstructors();for (Constructor constructor : constructors) {String name = constructor.getName();System.out.print(" ");String modifiers = Modifier.toString(constructor.getModifiers());if (modifiers.length() > 0) {System.out.print(modifiers + " ");}System.out.print(name + "(");Class[] paramTypes = constructor.getParameterTypes();for (int i = 0; i < paramTypes.length; i++) {if (i > 0) {System.out.print(", ");}System.out.print(paramTypes[i].getName());}System.out.println(");");}}/*** Prints all methods of a class** @param clazz*/public static void printMethods(Class clazz) {Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {Class returnType = method.getReturnType();String name = method.getName();System.out.print(" ");String modifiers = Modifier.toString(method.getModifiers());if (modifiers.length() > 0) {System.out.print(modifiers + " ");}System.out.print(returnType.getName() + " " + name + "(");Class[] paramTypes = method.getParameterTypes();for (int i = 0; i < paramTypes.length; i++) {if (i > 0) {System.out.print(", ");}System.out.print(paramTypes[i].getName());}System.out.println(");");}}/*** Prints all fields of a class** @param clazz*/public static void printFields(Class clazz) {Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {Class type = field.getType();String name = field.getName();System.out.print(" ");String modifiers = Modifier.toString(type.getModifiers());if (modifiers.length() > 0) {System.out.print(modifiers + " ");}System.out.println(type.getName() + " " + name + ";");}}}
运行程序,输出:
public final class java.lang.Double extends java.lang.Number{public java.lang.Double(double);public java.lang.Double(java.lang.String);public boolean equals(java.lang.Object);public static java.lang.String toString(double);public java.lang.String toString();public int hashCode();public static int hashCode(double);public static double min(double, double);public static double max(double, double);public static native long doubleToRawLongBits(double);public static long doubleToLongBits(double);public static native double longBitsToDouble(long);public volatile int compareTo(java.lang.Object);public int compareTo(java.lang.Double);public byte byteValue();public short shortValue();public int intValue();public long longValue();public float floatValue();public double doubleValue();public static java.lang.Double valueOf(java.lang.String);public static java.lang.Double valueOf(double);public static java.lang.String toHexString(double);public static int compare(double, double);public static boolean isNaN(double);public boolean isNaN();public static boolean isFinite(double);public static boolean isInfinite(double);public boolean isInfinite();public static double sum(double, double);public static double parseDouble(java.lang.String);public abstract final double POSITIVE_INFINITY;public abstract final double NEGATIVE_INFINITY;public abstract final double NaN;public abstract final double MAX_VALUE;public abstract final double MIN_NORMAL;public abstract final double MIN_VALUE;public abstract final int MAX_EXPONENT;public abstract final int MIN_EXPONENT;public abstract final int SIZE;public abstract final int BYTES;public final java.lang.Class TYPE;public abstract final double value;public abstract final long serialVersionUID;}Process finished with exit code 0
利用反射分析对象
从前面一节中,我们已经知道如何利用 Field、Method 和 Constructor 中提供的方法来分析类的能力。本节将进一步利用这三个反射类来分析对象。
修改一下 Cat 类,下面继续使用该类演示反射功能。
public class Cat {private String name = "小黑";public int age = 1;public Cat() {}private Cat(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public int getAge() {return age;}private void setName(String name) {this.name = name;}public static void printCat(String name, int age) {System.out.println("猫的名字:" + name + ",猫的年龄:" + age);}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\'' +", age=" + age +'}';}}
利用 Constructor 类中的方法动态的创建类的实例
- 首先,我们需要先获得某个 Constructor 对象;
 通过这个 Constructor 对象创建指定类的实例。
public static void main(String[] args) throws Exception {Class cls = Cat.class;// 1.获得无参Constructor对象Constructor constructor1 = cls.getConstructor();// 利用无参构造函数创建Cat类的实例Cat cat1 = (Cat) constructor1.newInstance(null);// 会调用cat的toString方法System.out.println(cat1);// 2.获得有参私有Constructor对象Constructor constructor2 = cls.getDeclaredConstructor(String.class, int.class);// 利用有参构造函数创建Cat类的实例Cat cat2 = (Cat) constructor2.newInstance("小白", 2);// 会调用cat的toString方法System.out.println(cat2);}
运行程序,输出:
Cat{name='小黑', age=1}Exception in thread "main" java.lang.IllegalAccessException: class test1.Test10 cannot access a member of class test1.Cat with modifiers "private"at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)at test1.Test10.main(Test10.java:24)
我们已经注意到在获取私有的构造函数的时候需要使用
getDeclaredConstructor方法,但是上面的代码任然抛出 IllegalAccessException 异常,什么原因?
Java 程序受到安全管理器的控制,只允许查看任意对象有哪些构造函数,而不允许调用 Constructor 的 newInstance 方法创建类的实例。同样的,Field 的 get 方法和 Method 的 invoke 方法同样存在这样的问题。
为了让 Java 程序不受到安全管理器的控制,需要调用 Field、Method 或 Constructor 对象的 setAccessible 方法。setAccessible 方法是 AccessibleObject 类中的一个方法,它是 Field、Method 和 Constructor 类的公共超类。
比如上面的代码可以添加:constructor2.setAccessible(true);
public static void main(String[] args) throws Exception {Class cls = Cat.class;// 1.获得无参Constructor对象Constructor constructor1 = cls.getConstructor();// 利用无参构造函数创建Cat类的实例Cat cat1 = (Cat) constructor1.newInstance(null);// 会调用cat的toString方法System.out.println(cat1);// 2.获得有参私有Constructor对象Constructor constructor2 = cls.getDeclaredConstructor(String.class, int.class);// 利用有参构造函数创建Cat类的实例constructor2.setAccessible(true);Cat cat2 = (Cat) constructor2.newInstance("小白", 2);// 会调用cat的toString方法System.out.println(cat2);}
运行程序,输出:
Cat{name='小黑', age=1}Cat{name='小白', age=2}
这样就不会抛出异常了。
利用 Field 类中的方法操作域
- 首先,我们需要先获得某个域的 Field 对象;
 通过这个 Field 对象操作指定的域。
public static void main(String[] args) throws Exception {Cat cat = new Cat();Class cls = Cat.class;// 1.获得共有字段age的Field对象Field ageField = cls.getField("age");// age是int类型,可以使用Field类中的getInt方法int ageVal = ageField.getInt(cat);System.out.println("第1次 age:" + ageVal);// 使用setInt方法重设age的值ageField.setInt(cat, 2);ageVal = ageField.getInt(cat);System.out.println("第2次 age:" + ageVal);// 2.获得私有字段name的Field对象Field nameField = cls.getDeclaredField("name");// 同样私有方法需要设置setAccessible方法,获得访问权nameField.setAccessible(true);// name是String类型,可以使用Field类中的get方法String nameVal = (String) nameField.get(cat);System.out.println("第1次 name:" + nameVal);// 使用set方法重设name的值nameField.set(cat, "小白");nameVal = (String) nameField.get(cat);System.out.println("第2次 name:" + nameVal);}
运行程序,输出:
第1次 age:1第2次 age:2第1次 name:小黑第2次 name:小白
同样的,name 是一个私有域,所以需要调用 setAccessible 方法,获得访问权。另外,如果域是 int 类型,可以使用 Field 类中的 getInt,setInt 方法操作该域,其他基本类型也有对应的方法。
利用 Method 类中的方法动态的执行实例的方法
- 首先,我们需要先获得某个方法的 Method 对象;
 通过这个 Method 对象操作指定的方法。
public static void main(String[] args) throws Exception {Cat cat = new Cat();Class cls = Cat.class;// 1.获得共有方法getName的Method对象Method method1 = cls.getMethod("getName");// 执行方法String name = (String) method1.invoke(cat);System.out.println("猫的名字:" + name);// 2.获得私有方法setName的Method对象Method method2 = cls.getDeclaredMethod("setName", String.class);// 执行方法method2.setAccessible(true);method2.invoke(cat, "小白");System.out.println(cat);// 3.获得静态方法printCat的Method对象Method method3 = cls.getMethod("printCat", String.class, int.class);// 执行静态方法,不需要传Cat对象method3.invoke(null, "小白", 2);}
运行程序,输出:
猫的名字:小黑Cat{name='小白', age=1}猫的名字:小白,猫的年龄:2
如果返回类型是基本类型,invoke 方法会返回其包装器类型。比如,method1 表示 Cat 类的 getAge 方法,那么返回的对象实际上是一个 Integer,必须相应地完成类型转换,可以使用自动拆箱将它转换为一个 int:
int age = (Integer) method1.invoke(cat);
如果存在多个相同名字的方法,可以通过方法的参数类型来区分。比如,还存在一个 getName 方法,需要传一个 String 类型的参数,则可以通过cls.getMethod("getName", String.class);来区分方法。
如果调用的方法是一个静态方法,invoke 方法的第一个参数传 null。
引用一段《Java 核心技术 卷1 基础知识》中描述 Method 类的内容:
上述程序清楚地表明,可以使用 method 对象实现 C 语言中函数指针的所有操作。同 C 一样,这种程序设计风格并不太简便,出错的可能性也比较大。如果在调用方法的时候提供了一个错误的参数,那么 invoke 方法将会抛出一个异常。
另外,invoke 的参数和返回值必须是 Object 类型的。这就意味着必须进行多次的类型转换。这样做将会使编译器错过检查代码的机会。因此,等到测试阶段才会发现这些错误,找到并改正它们将会更加困难。不仅如此,使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。
有鉴于此,建议仅在必要的时候才使用 Method 对象,而最好使用接口以及 Java SE 8 中的 lambda 表达式。特别要重申:建议 Java 开发者不要使用 Method 对象的回调功能。使用接口进行回调会使得代码的执行速度更快,更易于维护。
反射的优点和缺点
- 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
 - 缺点:使用反射基本是解释执行,对执行速度有影响。
 
我们用一个例子来比较用传统方式调用方法和反射方式调用方法,两者的执行速度是不是差别很大。
public class Test7 {public static void main(String[] args) throws Exception {m1();m2();}public static void m1() {Cat cat = new Cat();long startTime = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {cat.hi();}long endTime = System.currentTimeMillis();System.out.println("传统方式调用 hi 方法执行时间:" + (endTime - startTime));}public static void m2() throws Exception {Class cls = Class.forName("test15.Cat");Object o = cls.newInstance();Method hi = cls.getMethod("hi");long startTime = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {hi.invoke(o);}long endTime = System.currentTimeMillis();System.out.println("反射方式调用 hi 方法执行时间:" + (endTime - startTime));}}
运行程序,输出:
传统方式调用 hi 方法执行时间:3反射方式调用 hi 方法执行时间:270
对比两者的执行时间,确实差别比较大。有没有办法优化反射的性能?
反射优化的空间有限,可以通过关闭访问检查来优化一些反射的性能。
什么是访问检查?
Method、Field 和 Constructor 对象都有 setAccessible() 方法,setAccessible() 的作用是启用和禁用访问安全检查,当传入方法的参数值为 true 时,表示反射的对象在使用时取消访问检查,提高反射的效率;当传入方法的参数值为 false 时,表示反射的对象在使用时需要执行访问检查。
打开 Field 的类图,它继承了 AccessibleObject 类,在 AccessibleObject 类中有 setAccessible 方法。
 
修改上面的代码,在调用 hi() 方法之前,先执行 hi.setAccessible(true); 取消访问检查。
public class Test7 {public static void main(String[] args) throws Exception {m1();m2();}public static void m1() {Cat cat = new Cat();long startTime = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {cat.hi();}long endTime = System.currentTimeMillis();System.out.println("传统方式调用 hi 方法执行时间:" + (endTime - startTime));}public static void m2() throws Exception {Class cls = Class.forName("test15.Cat");Object o = cls.newInstance();Method hi = cls.getMethod("hi");hi.setAccessible(true);long startTime = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {hi.invoke(o);}long endTime = System.currentTimeMillis();System.out.println("反射方式调用 hi 方法执行时间:" + (endTime - startTime));}}
运行程序,输出:
传统方式调用 hi 方法执行时间:3反射方式调用 hi 方法执行时间:124
参考
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/lkv999 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
