Java反射机制
# Java反射机制
# 1. 什么是反射机制 (Reflection)?
简单来说,Java 反射机制指的是程序在运行状态中:
- 自省 (Introspection): 对于任意一个类,都能够知道这个类的所有组成部分,包括它的字段(成员变量)、方法、构造器、父类、实现的接口、注解等等。
- 操作 (Manipulation): 对于任意一个对象,都能够动态地调用它的任意方法、访问或修改它的任意字段(甚至包括私有成员),以及动态地创建这个类的实例(通过构造器)。
这种在运行时动态获取类的信息以及动态调用对象方法/属性的功能,就被称为 Java 语言的反射机制。它打破了编译时期的静态绑定,赋予了程序在运行时发现和使用代码(以 .class
字节码文件形式存在)的能力。
# 2. 反射机制的核心作用与应用场景
反射机制的主要作用体现在其动态性上:
- 动态加载类与创建对象: 在运行时根据配置文件、网络传输等信息加载未知的类,并创建其实例。许多框架的依赖注入 (DI) / 控制反转 (IoC) 容器就是基于此原理。
- 动态调用方法与访问属性: 无需在编译时知道具体方法名或属性名,可以在运行时根据字符串等信息来调用方法或操作属性。常见于通用框架、工具类、代码生成器等。
- 框架设计: 大量框架(如 Spring、Hibernate/MyBatis、JUnit 等)广泛使用反射来实现配置解析、对象关系映射、依赖注入、AOP(动态代理)、注解处理等核心功能。
- IDE 和调试工具: IDE 的代码提示、自动完成、调试器中的变量查看等功能,都离不开反射来探知代码结构。
- 注解处理器: 在编译期或运行时读取注解信息并进行相应处理。
核心包: 反射机制相关的核心类都位于 java.lang.reflect
包下。
# 3. 反射机制相关的核心类
理解 Java 反射,首先要掌握以下几个关键的类:
类名 | 含义与作用 |
---|---|
java.lang.Class<T> | 反射的入口点。代表一个类或接口在 JVM 中的字节码描述。每个加载到 JVM 中的类都有一个对应的 Class 对象。通过它可以获取类的所有信息。 |
java.lang.reflect.Method | 代表类中的方法。封装了方法的所有信息(名称、返回类型、参数、修饰符、注解等),并提供了调用该方法的能力 (invoke )。 |
java.lang.reflect.Constructor<T> | 代表类中的构造方法。封装了构造方法的所有信息(参数、修饰符、注解等),并提供了创建该类实例的能力 (newInstance )。 |
java.lang.reflect.Field | 代表类中的字段 (成员变量),包括实例变量和静态变量。封装了字段的所有信息(名称、类型、修饰符、注解等),并提供了读取和设置该字段值的能力 (get , set )。 |
关键关系: 要获取 Method
、Constructor
或 Field
对象,必须首先获得目标类的 Class
对象。Class
对象是进行反射操作的基础。
/**
* 示例类:定义了字段、构造方法和普通方法
*/
public class User {
// Field (字段)
private int age;
public String name = "DefaultName";
// Constructor (构造方法)
public User() { // 无参构造
System.out.println("User 无参构造被调用");
}
public User(int age, String name) { // 有参构造
this.age = age;
this.name = name;
System.out.println("User 有参构造被调用");
}
// Method (方法)
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
private void privateMethod() { // 私有方法
System.out.println("这是一个私有方法");
}
public static void staticMethod() { // 静态方法
System.out.println("这是一个静态方法");
}
}
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
# 4. 获取 Class
对象的三种主要方式
要对一个类进行反射操作,第一步就是获取该类的 Class
对象实例。常用的方式有三种:
获取方式 | 备注与特点 |
---|---|
1. Class.forName("类的全限定名") | 静态方法。最常用,通常用于加载驱动或在运行时根据字符串动态加载类。会触发类的初始化(执行静态代码块)。 |
2. 实例对象.getClass() | 通过已有的对象实例获取其对应的 Class 对象。 |
3. 类名.class (或 基本类型.class ) | 静态属性。最安全、性能最好(编译时检查)。不会触发类的初始化。适用于编译时已知具体类的情况。 |
注意: 这三种方式最终返回的都是同一个类的唯一的 Class
对象实例。
import java.util.Date;
/**
* 演示获取 Class 对象的三种方式
*/
public class GetClassDemo {
public static void main(String[] args) {
try {
// 方式一:使用 Class.forName("类的全限定名")
// 需要提供类的完整包名 + 类名
// 如果类不存在,会抛出 ClassNotFoundException
// 这种方式会执行类的静态初始化块
Class<?> userClass1 = Class.forName("com.example.reflect.User"); // User 类在 com.example.reflect 包下
System.out.println("方式一获取: " + userClass1);
// 方式二:使用 实例对象.getClass()
// 需要先有一个该类的实例
User userInstance = new User();
Class<?> userClass2 = userInstance.getClass();
System.out.println("方式二获取: " + userClass2);
// 对于基本数据类型,需要使用包装类
Date date = new Date();
Class<?> dateClass = date.getClass();
System.out.println("Date 对象的 Class: " + dateClass);
// 方式三:使用 类名.class
// 最直接、安全的方式,编译时即可检查类是否存在
// 这种方式不会触发类的静态初始化块 (除非后续操作触发)
Class<?> userClass3 = User.class;
System.out.println("方式三获取: " + userClass3);
// 也适用于基本数据类型和 void
Class<Integer> intClass = int.class; // 获取 int 基本类型的 Class 对象
Class<Void> voidClass = void.class; // 获取 void 类型的 Class 对象
System.out.println("int 的 Class: " + intClass);
System.out.println("void 的 Class: " + voidClass);
// 验证三种方式获取的是同一个 Class 对象实例
System.out.println("userClass1 == userClass2 ? " + (userClass1 == userClass2)); // true
System.out.println("userClass2 == userClass3 ? " + (userClass2 == userClass3)); // true
} catch (ClassNotFoundException e) {
System.err.println("类未找到: " + e.getMessage());
}
}
}
// User 类
// package com.example.reflect;
// public class User {
// static { System.out.println("User 类静态初始化块执行"); }
// }
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
50
51
# 5. 通过反射实例化对象 (Class.newInstance()
- 已过时)
获取到 Class
对象后,一种创建该类实例的方式是调用 Class
对象的 newInstance()
方法。
语法: Object obj = class对象.newInstance();
重要限制:
- 此方法只能调用类的公共 (public) 且无参数的构造方法。
- 如果类没有公共的无参构造器,或者构造器是私有的,调用
newInstance()
会抛出InstantiationException
或IllegalAccessException
。 - 已过时 (Deprecated since Java 9): 从 Java 9 开始,
Class.newInstance()
方法已被标记为过时。推荐使用Constructor
类的newInstance()
方法 (详见第 9 节),因为它功能更强大(可以调用任意参数的构造器,包括私有的)且能更好地处理异常。
/**
* 使用 Class.newInstance() (已过时) 实例化对象的示例
*/
public class ReflectNewInstanceDemo {
public static void main(String[] args) {
try {
// 获取 User 类的 Class 对象
Class<?> userClass = Class.forName("com.example.reflect.User"); // 替换为你的 User 类全路径
// 使用 newInstance() 创建 User 对象
// !!! 前提:User 类必须有一个 public 的无参构造方法 !!!
// !!! 此方法自 Java 9 起已过时 !!!
Object userObj = userClass.newInstance(); // 底层调用的是无参构造器
// userObj 的编译时类型是 Object,如果需要调用 User 特有方法,需要强制类型转换
// if (userObj instanceof User) {
// User user = (User) userObj;
// // ... 可以调用 user 的方法 ...
// }
System.out.println("通过反射 (Class.newInstance) 创建的对象: " + userObj);
} catch (ClassNotFoundException e) {
System.err.println("类未找到: " + e.getMessage());
} catch (InstantiationException e) {
// 如果类是抽象类、接口、数组类,或者没有无参构造器时抛出
System.err.println("实例化异常 (可能缺少无参构造器或类无法实例化): " + e.getMessage());
} catch (IllegalAccessException e) {
// 如果无参构造器不是 public 的,抛出此异常
System.err.println("非法访问异常 (无参构造器不可访问): " + e.getMessage());
}
}
}
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
# 6. 妙用 Class.forName()
:仅触发类加载与静态初始化
有时我们只想加载一个类并执行其静态代码块 (static initializer block),而不需要创建该类的实例。这在某些场景下非常有用,例如经典的 JDBC 驱动加载。
使用 Class.forName("完整类名")
方法可以实现这个目的:
- 触发类加载: JVM 会查找并加载指定的
.class
文件。 - 执行静态初始化: 在类加载过程的初始化阶段,该类中定义的静态代码块会被自动执行。静态代码块通常用于初始化静态变量、注册驱动或执行只需进行一次的设置操作。
- 不创建实例: 该方法本身并不会创建类的对象实例。
/**
* 演示一个仅包含静态代码块的类
*/
public class DriverInitializer {
// 静态代码块:在类被加载到 JVM 并初始化时执行,且只执行一次
static {
System.out.println("DriverInitializer 类的静态代码块正在执行...");
// 这里可以放置一些初始化逻辑,例如注册驱动、加载配置文件等
// 模拟注册操作
System.out.println("模拟驱动注册完成。");
}
// 类可以有其他成员,但 Class.forName 不会直接调用它们
public DriverInitializer() {
System.out.println("DriverInitializer 构造方法执行 (不会被 Class.forName 调用)");
}
public static void staticMethod() {
System.out.println("DriverInitializer 静态方法执行 (不会被 Class.forName 调用)");
}
}
/**
* 演示使用 Class.forName 触发类加载和静态初始化
*/
public class ClassForNameInitDemo {
public static void main(String[] args) {
System.out.println("准备加载 DriverInitializer 类...");
try {
// 调用 Class.forName(),只指定类名
// 这将导致 DriverInitializer 类被加载、链接和初始化
// 其静态代码块会在此过程中被执行
Class<?> loadedClass = Class.forName("com.example.reflect.DriverInitializer"); // 替换为实际包名
System.out.println("DriverInitializer 类已加载。Class 对象: " + loadedClass);
// 注意:此时 DriverInitializer 的构造方法和静态方法都没有被调用
// 如果需要实例,后续可以再创建 (但这非 Class.forName 的主要目的)
// Object instance = loadedClass.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
System.err.println("类未找到: " + e.getMessage());
}
// } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
// System.err.println("创建实例时出错: " + e.getMessage());
// }
System.out.println("主程序结束。");
}
}
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
JDBC 驱动加载: 在早期 JDBC 中,Class.forName("com.mysql.jdbc.Driver")
就是利用这个机制来加载 MySQL 驱动类,驱动类的静态代码块会执行 DriverManager.registerDriver(new Driver())
将自己注册到 DriverManager
中。
# 7. 反射之 Field
:访问和修改字段
Field
类代表类的成员变量(字段)。通过反射可以获取 Field
对象,然后读取或修改任意对象(包括实例对象和静态字段所属的类对象)的该字段的值,即使是私有字段。
# Class
类中与 Field
相关的方法
方法名 | 描述 |
---|---|
getFields() | 返回一个 Field 数组,包含该类及其所有父类中声明为 public 的字段。 |
getDeclaredFields() | 返回一个 Field 数组,包含在该类中直接声明的所有字段(包括 private , protected , package-private , public ),不包括父类的字段。 |
getField(String name) | 根据字段名 name 获取一个 public 的字段(可能在当前类或父类中)。如果字段不存在或不是 public ,抛出 NoSuchFieldException 。 |
getDeclaredField(String name) | 根据字段名 name 获取在当前类中直接声明的指定字段(不限访问修饰符)。如果字段不存在于当前类,抛出 NoSuchFieldException 。 常用于获取私有字段。 |
getModifiers() (在 Field 类中) | 返回字段的修饰符(如 public , private , static , final )对应的整数表示。通常配合 java.lang.reflect.Modifier.toString() 使用。 |
# Field
类中的核心方法
方法名 | 描述 |
---|---|
getName() | 返回字段的名称 (String)。 |
getType() | 返回字段的类型对应的 Class 对象 (例如 int.class , String.class )。 |
getModifiers() | 返回字段修饰符的整数表示。 |
set(Object obj, Object value) | 设置字段的值。obj 是要修改字段的对象实例 (如果是静态字段,obj 传入 null );value 是要设置的新值。需要注意类型匹配和访问权限。 |
get(Object obj) | 读取字段的值。obj 是要读取字段的对象实例 (如果是静态字段,obj 传入 null )。返回字段的当前值 (Object 类型)。需要注意访问权限。 |
setAccessible(boolean flag) | 打破封装。核心方法!默认 false 。如果设为 true ,则可以无视 Java 的访问控制(private , protected ),直接访问或修改私有字段。 |
# 通过反射设置和读取对象属性 (包括私有)
import java.lang.reflect.Field;
/**
* 演示通过反射访问和修改对象字段(包括私有字段)
*/
public class ReflectFieldDemo {
public static void main(String[] args) {
try {
// 1. 获取 Student 类的 Class 对象
Class<?> studentClass = Class.forName("com.example.reflect.Student"); // 替换为你的 Student 类全路径
// 2. 创建 Student 对象实例 (假设有无参构造)
Object studentObj = studentClass.getDeclaredConstructor().newInstance(); // 推荐使用 Constructor 创建
// --- 操作 public 字段 'grade' ---
System.out.println("--- 操作 public 字段 'grade' ---");
// 2.1 获取名为 'grade' 的 public 字段
Field gradeField = studentClass.getField("grade"); // getField 只能获取 public 的
System.out.println("获取到字段: " + gradeField.getName() + ", 类型: " + gradeField.getType().getSimpleName());
// 2.2 设置 'grade' 字段的值
// set(对象实例, 值)
gradeField.set(studentObj, 3); // 设置年级为 3
// 2.3 读取 'grade' 字段的值
// get(对象实例)
Object gradeValue = gradeField.get(studentObj);
System.out.println("设置后 grade 的值: " + gradeValue); // 输出 3
// --- 操作 private 字段 'name' ---
System.out.println("\n--- 操作 private 字段 'name' ---");
// 3.1 获取名为 'name' 的 private 字段
// 必须使用 getDeclaredField,因为 getField 找不到 private 字段
Field nameField = studentClass.getDeclaredField("name");
System.out.println("获取到字段: " + nameField.getName() + ", 类型: " + nameField.getType().getSimpleName());
// 3.2 !!! 打破封装(关键步骤)!!!
// 如果不设置 accessible 为 true,访问私有字段会抛出 IllegalAccessException
nameField.setAccessible(true);
// 3.3 设置私有 'name' 字段的值
nameField.set(studentObj, "张三");
// 3.4 读取私有 'name' 字段的值
Object nameValue = nameField.get(studentObj);
System.out.println("设置后 name 的值: " + nameValue); // 输出 张三
// --- 操作 private static 字段 'school' ---
System.out.println("\n--- 操作 private static 字段 'school' ---");
Field schoolField = studentClass.getDeclaredField("school");
System.out.println("获取到静态字段: " + schoolField.getName());
schoolField.setAccessible(true); // 同样需要打破封装
// 3.5 设置静态字段的值,第一个参数传 null
schoolField.set(null, "清华大学");
// 3.6 读取静态字段的值,第一个参数传 null
Object schoolValue = schoolField.get(null);
System.out.println("设置后静态 school 的值: " + schoolValue); // 输出 清华大学
} catch (Exception e) { // 捕获所有可能的反射相关异常
e.printStackTrace();
}
}
}
// Student 类
// package com.example.reflect;
// class Student {
// public int grade; // Public 字段
// private String name; // Private 字段
// private static String school = "默认学校"; // Private static 字段
//
// public Student() {} // 无参构造
//
// @Override
// public String toString() {
// return "Student{" + "grade=" + grade + ", name='" + name + '\'' + ", school='" + school + '\'' + '}';
// }
// }
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
注意 setAccessible(true)
:
- 这是反射强大之处,但也破坏了类的封装性,可能违反设计意图。
- 可能带来安全风险,允许外部代码修改不应被修改的内部状态。
- 在某些安全管理器 (Security Manager) 环境下,调用
setAccessible(true)
可能会失败。 - 应谨慎使用,通常只在框架、测试或特定工具开发中使用。
# 反编译 Field
(了解)
可以通过反射获取一个类的所有字段信息,模拟反编译的效果。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* 通过反射反编译类的字段信息 (了解即可)
*/
public class ReflectDecompileField {
public static void main(String[] args) {
try {
// 要反编译的类
Class<?> targetClass = Class.forName("com.example.reflect.Student"); // 替换为你的类名
StringBuilder sb = new StringBuilder();
// 获取类的修饰符 (public, class 等) 和简单名称
sb.append(Modifier.toString(targetClass.getModifiers()))
.append(" class ")
.append(targetClass.getSimpleName())
.append(" {\n");
// 获取类中直接声明的所有字段 (包括私有)
Field[] declaredFields = targetClass.getDeclaredFields();
for (Field field : declaredFields) {
sb.append("\t"); // 缩进
// 获取字段的修饰符 (public, private, static, final 等)
sb.append(Modifier.toString(field.getModifiers()));
if (field.getModifiers() != 0) { // 如果有修饰符,加个空格
sb.append(" ");
}
// 获取字段的类型 (简单名称)
sb.append(field.getType().getSimpleName());
sb.append(" ");
// 获取字段的名称
sb.append(field.getName());
sb.append(";\n"); // 添加分号和换行
}
sb.append("}"); // 类结束
System.out.println(sb.toString()); // 打印反编译结果
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
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
50
# 8. 反射之 Method
:动态调用方法
Method
类代表类中的方法。通过反射可以获取 Method
对象,然后动态地调用任意对象(或静态方法所属的类)的该方法,即使是私有方法。
# Class
类中与 Method
相关的方法
方法名 | 描述 |
---|---|
getMethods() | 返回一个 Method 数组,包含该类及其所有父类中声明为 public 的方法。 |
getDeclaredMethods() | 返回一个 Method 数组,包含在该类中直接声明的所有方法(包括 private , protected , package-private , public ),不包括父类的方法。 |
getMethod(String name, Class<?>... parameterTypes) | 根据方法名 name 和参数类型列表 parameterTypes 获取一个 public 的方法(可能在当前类或父类中)。如果方法不存在或不是 public ,抛出 NoSuchMethodException 。 |
getDeclaredMethod(String name, Class<?>... parameterTypes) | 根据方法名 name 和参数类型列表 parameterTypes 获取在当前类中直接声明的指定方法(不限访问修饰符)。如果方法不存在于当前类,抛出 NoSuchMethodException 。 常用于获取私有方法。 |
# Method
类中的核心方法
方法名 | 描述 |
---|---|
getName() | 返回方法名 (String)。 |
getReturnType() | 返回方法返回值类型的 Class 对象。 |
getParameterTypes() | 返回一个 Class 数组,包含方法所有参数的类型。 |
getModifiers() | 返回方法修饰符的整数表示。 |
invoke(Object obj, Object... args) | 动态调用方法。obj 是调用该方法的对象实例 (如果是静态方法,obj 传入 null );args 是传递给方法的实际参数列表 (可变参数)。返回方法的返回值 (Object 类型,如果是 void 方法则返回 null )。需要注意访问权限和参数匹配。 |
setAccessible(boolean flag) | 打破封装。同样可以用于方法,设为 true 后可以调用私有方法。 |
# 通过反射调用对象方法 (包括私有和静态)
import java.lang.reflect.Method;
/**
* 演示通过反射调用对象方法
*/
public class ReflectMethodDemo {
public static void main(String[] args) {
try {
// 1. 获取 UserService 类的 Class 对象
Class<?> userServiceClass = Class.forName("com.example.reflect.UserService"); // 替换为你的类全路径
// 2. 创建 UserService 对象实例
Object userServiceObj = userServiceClass.getDeclaredConstructor().newInstance();
// --- 调用 public 方法 login(String, String) ---
System.out.println("--- 调用 public 方法 login ---");
// 3.1 获取 login 方法的 Method 对象
// 需要提供方法名和参数类型列表 (Class 对象)
Method loginMethod = userServiceClass.getMethod("login", String.class, String.class);
// 3.2 调用方法:invoke(对象实例, 参数...)
Object loginResult = loginMethod.invoke(userServiceObj, "admin", "password123");
System.out.println("login 方法返回值: " + loginResult); // 输出 true 或 false
// --- 调用 private 方法 checkCredentials(String) ---
System.out.println("\n--- 调用 private 方法 checkCredentials ---");
// 4.1 获取私有方法的 Method 对象,必须用 getDeclaredMethod
Method checkMethod = userServiceClass.getDeclaredMethod("checkCredentials", String.class);
// 4.2 !!! 打破封装 !!!
checkMethod.setAccessible(true);
// 4.3 调用私有方法
Object checkResult = checkMethod.invoke(userServiceObj, "secret-password");
System.out.println("checkCredentials 方法返回值: " + checkResult);
// --- 调用 public static 方法 validateInput(String) ---
System.out.println("\n--- 调用 public static 方法 validateInput ---");
// 5.1 获取静态方法的 Method 对象
Method validateMethod = userServiceClass.getMethod("validateInput", String.class);
// 5.2 调用静态方法:invoke 的第一个参数传 null
Object validateResult = validateMethod.invoke(null, "valid-input");
System.out.println("validateInput 方法返回值: " + validateResult);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// UserService 类
// package com.example.reflect;
// class UserService {
// public boolean login(String username, String password) {
// System.out.println("调用 login 方法: username=" + username + ", password=" + password);
// return "admin".equals(username) && checkCredentials(password);
// }
//
// private boolean checkCredentials(String password) {
// System.out.println("调用私有 checkCredentials 方法: password=" + password);
// return "password123".equals(password) || "secret-password".equals(password);
// }
//
// public static boolean validateInput(String input) {
// System.out.println("调用静态 validateInput 方法: input=" + input);
// return input != null && !input.isEmpty();
// }
//
// public UserService(){} // 无参构造
// }
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
invoke()
方法注意事项:
- 第一个参数
obj
: 对于实例方法,必须传入调用该方法的对象实例;对于静态方法,传入null
。 - 第二个参数
args
: 传递给方法的实际参数列表。如果方法没有参数,则不传或传null
;如果方法有参数,必须按照方法签名的顺序和类型传入对应的参数值。 - 返回值:
invoke()
方法返回Object
类型,表示被调用方法的实际返回值。如果被调用方法返回void
,则invoke()
返回null
。如果被调用方法返回基本类型,invoke()
会返回其对应的包装类实例。 - 异常: 如果被调用的方法本身抛出异常,这个异常会被包装在
InvocationTargetException
中抛出。需要通过getCause()
获取原始异常。
# 反编译 Method
(了解)
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter; // Java 8+ 获取参数名
/**
* 通过反射反编译类的方法信息 (了解即可)
*/
public class ReflectDecompileMethod {
public static void main(String[] args) {
try {
// 要反编译的类
Class<?> targetClass = Class.forName("com.example.reflect.UserService"); // 替换为你的类名
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(targetClass.getModifiers()))
.append(" class ")
.append(targetClass.getSimpleName())
.append(" {\n");
// 获取所有直接声明的方法
Method[] declaredMethods = targetClass.getDeclaredMethods();
for (Method method : declaredMethods) {
sb.append("\t"); // 缩进
// 方法修饰符
sb.append(Modifier.toString(method.getModifiers())).append(" ");
// 返回值类型
sb.append(method.getReturnType().getSimpleName()).append(" ");
// 方法名
sb.append(method.getName());
sb.append("(");
// 参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
// Java 8+ 可以尝试获取参数名 (需要在编译时开启 -parameters 选项)
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameterTypes.length; i++) {
sb.append(parameterTypes[i].getSimpleName());
// 尝试添加参数名
if (parameters != null && i < parameters.length && parameters[i].isNamePresent()) {
sb.append(" ").append(parameters[i].getName());
} else {
sb.append(" arg").append(i); // 默认参数名
}
if (i < parameterTypes.length - 1) {
sb.append(", ");
}
}
sb.append(") {\n\t\t// method body\n\t}\n"); // 方法体用注释代替
}
sb.append("}");
System.out.println(sb.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
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
50
51
52
53
54
55
56
57
58
59
60
61
# 9. 反射之 Constructor
:动态创建对象
Constructor
类代表类的构造方法。通过反射获取 Constructor
对象后,可以调用其 newInstance()
方法来创建类的实例,这种方式比 Class.newInstance()
更强大、更推荐。
# Class
类中与 Constructor
相关的方法
方法名 | 描述 |
---|---|
getConstructors() | 返回一个 Constructor 数组,包含该类所有 public 的构造方法。 |
getDeclaredConstructors() | 返回一个 Constructor 数组,包含在该类中直接声明的所有构造方法(包括 private , protected , package-private , public )。 |
getConstructor(Class<?>... parameterTypes) | 根据参数类型列表 parameterTypes 获取一个 public 的构造方法。如果不存在或不是 public ,抛出 NoSuchMethodException 。 |
getDeclaredConstructor(Class<?>... parameterTypes) | 根据参数类型列表 parameterTypes 获取在当前类中直接声明的指定构造方法(不限访问修饰符)。如果不存在,抛出 NoSuchMethodException 。 常用于获取私有构造器。 |
# Constructor
类中的核心方法
方法名 | 描述 |
---|---|
getName() | 返回构造方法名 (对于构造器,总是返回类的全限定名)。 |
getParameterTypes() | 返回一个 Class 数组,包含构造方法所有参数的类型。 |
getModifiers() | 返回构造方法修饰符的整数表示。 |
newInstance(Object... initargs) | 创建类的新实例。initargs 是传递给构造方法的实际参数列表。返回创建的对象实例 (类型为 T )。需要注意访问权限和参数匹配。这是推荐的反射创建对象方式。 |
setAccessible(boolean flag) | 打破封装。设为 true 后可以调用私有构造方法来创建实例。 |
# 创建对象的两步骤 (使用 Constructor)
- 获取
Constructor
对象: 使用clazz.getDeclaredConstructor(paramTypes...)
获取指定参数列表的构造方法(即使是私有的)。如果需要调用无参构造,则不传参数clazz.getDeclaredConstructor()
。 - 调用
newInstance()
创建实例: 调用获取到的Constructor
对象的newInstance(args...)
方法,并传入构造方法所需的实际参数。
# 示例:通过反射调用构造方法实例化对象
import java.lang.reflect.Constructor;
/**
* 演示通过反射调用构造方法创建对象 (推荐方式)
*/
public class ReflectConstructorDemo {
public static void main(String[] args) {
try {
// 1. 获取 Vip 类的 Class 对象
Class<?> vipClass = Class.forName("com.example.reflect.Vip"); // 替换为你的 Vip 类全路径
// --- 调用 public 无参构造方法 ---
System.out.println("--- 调用 public 无参构造 ---");
// 1.1 获取无参构造方法对象
Constructor<?> noArgConstructor = vipClass.getDeclaredConstructor(); // 无参,不传参数类型
// 1.2 调用 newInstance() 创建对象 (无参,不传参数)
Object vipObj1 = noArgConstructor.newInstance();
System.out.println("通过无参构造创建: " + vipObj1);
// --- 调用 public 有参构造方法 ---
System.out.println("\n--- 调用 public 有参构造 ---");
// 2.1 获取有参构造方法对象,需要指定参数类型
Constructor<?> argsConstructor = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
// 2.2 调用 newInstance() 创建对象,传入匹配的参数
Object vipObj2 = argsConstructor.newInstance(101, "李四", "2000-05-20", true);
System.out.println("通过有参构造创建: " + vipObj2);
// --- 调用 private 构造方法 (假设存在) ---
System.out.println("\n--- 调用 private 构造 (假设存在) ---");
try {
// 3.1 获取私有构造方法对象
Constructor<?> privateConstructor = vipClass.getDeclaredConstructor(String.class); // 有一个 private Vip(String name)
// 3.2 !!! 打破封装 !!!
privateConstructor.setAccessible(true);
// 3.3 调用 newInstance()
Object vipObj3 = privateConstructor.newInstance("私有构造创建");
System.out.println("通过私有构造创建: " + vipObj3);
} catch (NoSuchMethodException e) {
System.out.println("未找到指定的私有构造方法: " + e.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// Vip 类
// package com.example.reflect;
// import java.util.Date;
// class Vip {
// int no;
// String name;
// String birth; // 用 String 方便演示
// boolean sex;
//
// public Vip() { System.out.println("Vip public 无参构造"); }
//
// public Vip(int no, String name, String birth, boolean sex) {
// this.no = no; this.name = name; this.birth = birth; this.sex = sex;
// System.out.println("Vip public 有参构造");
// }
//
// private Vip(String name) { // 私有构造
// this.name = name;
// System.out.println("Vip private 构造 (name)");
// }
//
// @Override
// public String toString() { /* ... toString 实现 ... */ return "...";}
// }
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
笔记:`Class.newInstance()` vs `Constructor.newInstance()`
Class.newInstance()
(已过时):- 只能调用 public 无参 构造器。
- 异常处理不够明确(将构造器内部异常包装为
InvocationTargetException
嵌套在InstantiationException
或IllegalAccessException
中)。
Constructor.newInstance()
(推荐):- 可以调用任意访问权限(配合
setAccessible
)、任意参数的构造器。 - 异常处理更直接:如果构造器内部抛出异常,会直接包装在
InvocationTargetException
中抛出。 - 类型安全:返回的是泛型
T
,减少强制类型转换。
- 可以调用任意访问权限(配合
因此,现代 Java 开发中应始终优先使用 Constructor.newInstance()
来通过反射创建对象。
# 反编译 Constructor
(了解)
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
/**
* 通过反射反编译类的构造方法信息 (了解即可)
*/
public class ReflectDecompileConstructor {
public static void main(String[] args) {
try {
// 要反编译的类
Class<?> targetClass = Class.forName("com.example.reflect.Vip"); // 替换为你的类名
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(targetClass.getModifiers()))
.append(" class ")
.append(targetClass.getSimpleName())
.append(" {\n");
// 获取所有直接声明的构造方法
Constructor<?>[] declaredConstructors = targetClass.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
sb.append("\t"); // 缩进
// 构造方法修饰符
sb.append(Modifier.toString(constructor.getModifiers())).append(" ");
// 构造方法名 (就是类名)
sb.append(targetClass.getSimpleName());
sb.append("(");
// 参数列表
Class<?>[] parameterTypes = constructor.getParameterTypes();
Parameter[] parameters = constructor.getParameters(); // Java 8+
for (int i = 0; i < parameterTypes.length; i++) {
sb.append(parameterTypes[i].getSimpleName());
if (parameters != null && i < parameters.length && parameters[i].isNamePresent()) {
sb.append(" ").append(parameters[i].getName());
} else {
sb.append(" arg").append(i);
}
if (i < parameterTypes.length - 1) {
sb.append(", ");
}
}
sb.append(") {\n\t\t// constructor body\n\t}\n");
}
sb.append("}");
System.out.println(sb.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
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
50
51
52
53
54
55
56
57
# 10. 反射中如何区分类的成员
在进行反射操作时,准确地定位到你想要的类成员(字段、方法、构造器)至关重要。它们的区分方式略有不同:
- 字段 (Field): 主要通过字段名称 (Name) 来唯一识别。在同一个类中,字段名不能重复。获取
Field
对象通常使用getDeclaredField(String name)
。 - 方法 (Method): 由于 Java 支持方法重载 (Overloading),仅靠方法名不足以唯一确定一个方法。因此,方法的唯一标识是方法名 (Name) + 参数类型列表 (Parameter Types)。获取
Method
对象需要同时提供这两者,使用getDeclaredMethod(String name, Class<?>... parameterTypes)
。 - 构造方法 (Constructor): 构造方法没有显式名称(其名称隐含为类名)。它们的区分完全依赖于参数类型列表 (Parameter Types)。获取
Constructor
对象使用getDeclaredConstructor(Class<?>... parameterTypes)
。
理解这一点对于正确使用 getDeclaredField
, getDeclaredMethod
, getDeclaredConstructor
这三个核心查找方法至关重要。
# 11. 获取类的父类与实现的接口
反射还提供了获取一个类的继承和实现关系信息的能力。
获取父类:
Class<?> getSuperclass()
- 返回表示当前
Class
对象所代表的那个类的直接父类的Class
对象。 - 如果当前类是
Object
类、一个接口、一个基本类型或void
,则返回null
。
- 返回表示当前
获取实现的接口:
Class<?>[] getInterfaces()
- 返回一个
Class
数组,包含当前Class
对象所代表的类直接实现的所有接口。 - 顺序与类声明中
implements
子句后的接口顺序一致。 - 如果类没有实现任何接口,返回一个长度为 0 的数组。
- 注意: 此方法不返回父类实现的接口。如果需要获取所有接口(包括继承的),需要递归遍历父类的
getInterfaces()
。
- 返回一个
/**
* 演示获取父类和接口
*/
public class ReflectInheritanceDemo {
public static void main(String[] args) {
try {
// 以 ArrayList 为例
Class<?> arrayListClass = Class.forName("java.util.ArrayList");
// 获取父类
Class<?> superclass = arrayListClass.getSuperclass();
if (superclass != null) {
System.out.println(arrayListClass.getSimpleName() + " 的直接父类是: " + superclass.getName());
// 输出: ArrayList 的直接父类是: java.util.AbstractList
} else {
System.out.println(arrayListClass.getSimpleName() + " 没有直接父类 (可能是 Object 或接口)。");
}
// 获取直接实现的接口
Class<?>[] interfaces = arrayListClass.getInterfaces();
if (interfaces.length > 0) {
System.out.println(arrayListClass.getSimpleName() + " 直接实现的接口有:");
for (Class<?> intf : interfaces) {
System.out.println("\t- " + intf.getName());
// 输出可能包含:
// - java.util.List
// - java.util.RandomAccess
// - java.lang.Cloneable
// - java.io.Serializable
}
} else {
System.out.println(arrayListClass.getSimpleName() + " 没有直接实现任何接口。");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
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
# 12. 总结与思考
Java 反射机制是一把双刃剑。它赋予了程序在运行时进行自我检查和动态操作的强大能力,是实现许多高级功能和框架的基础。
优点:
- 动态性: 增加了程序的灵活性和扩展性,可以在运行时加载、探知、使用编译时未知的类。
- 通用性: 可以编写更通用的代码,适用于多种不同的类型。框架设计常用。
缺点:
- 性能开销: 反射操作(如方法调用
invoke
)通常比直接代码调用慢得多,因为它涉及更多的查找、检查和间接调用步骤。不应在性能敏感的核心路径上滥用。 - 破坏封装:
setAccessible(true)
可以绕过访问控制,访问和修改私有成员,破坏了类的封装性,可能导致代码难以理解和维护,并引入安全风险。 - 类型安全: 反射操作在编译时缺乏类型检查,许多问题(如类型不匹配、方法/字段不存在)需要到运行时才能发现,可能导致
RuntimeException
。 - 代码复杂度: 反射相关的代码通常比直接调用更冗长、更复杂,可读性较差。
何时使用:
- 开发通用框架(如 Spring IoC/AOP, ORM 框架, 序列化库)。
- 编写需要动态加载类或执行逻辑的工具(如插件系统、代码生成器)。
- 实现注解处理器。
- 测试或调试工具。
总而言之,反射是 Java 提供的强大武器,但应明智、谨慎地使用。理解其原理和潜在成本,才能在合适的场景下发挥其最大价值,同时避免其带来的负面影响。
以上便是本文的全部内容,本人才疏学浅,文章有什么错误的地方,欢迎大佬们批评指正!我是scholar,一个在互联网行业的小白,立志成为更好的自己。
如果你想了解更多关于scholar (opens new window),可以关注公众号-书生带你学编程,后面文章会首先同步至公众号。