Java反射
# Java反射
JDK8 Reflect API (opens new window)
# 1. 反射机制概念
反射机制允许程序在运行时取得任何一个已知名称 class 的内部信息,包括包括其 modifiers (修饰符),fields (属性),methods (方法)等,并可于运行时改变属性内容或调用方法。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;反射使用不当会造成很高的资源消耗!
优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点 :让我们在运行时有了分析操作类的能力,这将增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
# 2. 反射的应用场景
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制和动态代理,动态代理的实现也依赖反射;
Java 中的一大利器「注解」的实现也用到了反射。
# 3. Class类
# 3.1 反射机制相关类
反射机制的相关类在 java.lang.reflect.*
包下。
与反射机制相关的重要的类
类 | 含义 |
---|---|
java.lang.Class | 代表整个字节码 |
java.lang.reflect.Method | 代表字节码中的方法字节码 |
java.lang.reflect.Constructor | 代表字节码中的构造方法字节码 |
java.lang.reflect.Field | 代表字节码中的属性字节码 |
注:必须先获得Class才能获取Method、Constructor、Field
# 3.2 理解Class对象
要想理解反射,首先要理解 Class
类,因为 Class
类是实现反射的基础。
在程序运行期间,JVM 始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类的完整结构信息,包括包名、类名、实现的接口、拥有的方法和字段等。可以通过专门的 Java 类访问这些信息,这个类就是 Class
类。我们可以把 Class
类理解为类的类型,一个 Class
对象,称为类的类型对象,一个 Class
对象对应一个加载到 JVM 中的一个 .class
文件。
我们编写的 .java
程序代码通过 javac
编译器编为 .class
字节码文件。类的加载过程就是 JVM 加载一个 .class
文件的过程,该过程可分为以下3步:加载、链接和初始化。
在类的加载阶段,JVM会在堆中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。需要注意的是,每个类只有一个 Class
对象。
那么在加载完一个类后,堆内存的方法区就产生了一个 Class
对象,这个对象就包含了完整的类的结构信息,我们可以通过这个 Class
对象看到类的结构,就好比一面镜子。所以我们形象的称之为:反射。
在通常情况下,一定是先有类再有对象,我们把这个通常情况称为 “正”。那么反射中的这个 “反” 我们就可以理解为根据对象找到对象所属的类。
# 3.3 获取Class对象
从 Class
类的源码可以看出,它的构造函数是私有的,也就是说只有 JVM 可以创建 Class
类的对象,我们不能像普通类一样直接 new 一个 Class
对象。
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
...
/*
* 私有化构造器,只能由JVM创建Class对象,这个构造不会被使用,它会防止空参构造器的自动生成
*/
private Class(ClassLoader loader) {
classLoader = loader;
}
2
3
4
5
6
7
8
我们只能通过已有的类来得到一个 Class
类对象,Java 提供了四种方式:
- 知道具体类
Class clazz = TargetObject.class;
- 通过
Class.forName()
传入类的全路径获取
Class clazz = Class.forName("com.xxx.TargetObject");
- 通过对象实例
instance.getClass()
获取
TargetObject o = new TargetObject();
Class clazz = o.getClass();
2
- 通过类加载器
xxxClassLoader.loadClass()
传入类路径获取
Class clazz = ClassLoader.loadClass("com.xx.TargetObject");
注意:通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
# 4. 反射的基本操作
创建一个我们要使用反射操作的类 TargetObject
package com.zqc;
public class TargetObject {
private String value;
public TargetObject() {
value = "Reflect";
}
public void publicMethod(String s) {
System.out.println(s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用反射操作这个类的方法以及参数
package com.zqc;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/*
* 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
*/
Class<?> targetClass = Class.forName("com.zqc.TargetObject");
TargetObject targetObject = (TargetObject) targetClass.newInstance();
/*
* 获取 TargetObject 类中定义的所有方法
*/
Method[] methods = targetClass.getDeclaredMethods();
System.out.println("-----------获取类中的所有方法-----------");
for (Method method : methods) {
System.out.println(method.getName());
}
/*
* 获取指定方法并调用
*/
System.out.println("-----------调用publicMethod-----------");
Method publicMethod = targetClass.getDeclaredMethod("publicMethod", String.class);
publicMethod.invoke(targetObject, "publicTest");
/*
* 获取指定参数并对参数进行修改
*/
Field field = targetClass.getDeclaredField("value");
// 为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "Java");
/*
* 调用 private 方法
*/
System.out.println("-----------调用privateMethod-----------");
Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
// 为了调用private方法我们取消安全检查
// 默认false,设置为true为打破封装
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
控制台输出:
-----------获取类中的所有方法-----------
privateMethod
publicMethod
-----------调用publicMethod-----------
publicTest
-----------调用privateMethod-----------
value is Java
2
3
4
5
6
7