背景
本文是《Java 后端从小白到大神》修仙系列第十六篇
,正式进入Java后端
世界,本篇文章主要聊Java基础
。若想详细学习请点击首篇博文,我们开始把。
文章概览
- 反射与序列化
反射与序列化
一、反射(Reflection)
1. 概念
反射是 Java 在运行时动态获取类信息(如类名、方法、字段、注解等)并操作类或对象的能力。它突破了访问权限的限制,可以调用私有方法和修改私有字段。
2. 核心类
Class<T>
:类的元数据入口。
Constructor<T>
:构造方法。
Method
:普通方法。
Field
:字段。
Annotation
:注解。
3. 使用方法
1. 获取 Class 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 方式1:通过类名
Class<?> clazz = String.class;
// 方式2:通过对象实例
String str = "Hello";
Class<?> clazz = str.getClass();
// 方式3:通过全限定类名(需处理异常)
try {
Class<?> clazz = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
|
2. 创建对象实例
1
2
3
4
5
6
7
8
9
10
11
|
Class<?> clazz = String.class;
try {
// 使用无参构造器
String str1 = (String) clazz.newInstance();
// 使用带参构造器
Constructor<?> constructor = clazz.getConstructor(String.class);
String str2 = (String) constructor.newInstance("Hello");
} catch (Exception e) {
e.printStackTrace();
}
|
3. 调用方法
1. method.invoke() 方法的基本用法
1
2
|
// 方法签名
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
|
- obj:要调用方法的对象实例。如果方法是静态的,可以传递 null。
- args:方法的参数,可以是零个或多个参数。
- 返回值:方法的返回值,如果方法返回 void,则返回 null。
- 异常:
- IllegalAccessException:如果方法不可访问。
- IllegalArgumentException:如果参数不匹配。
- InvocationTargetException:如果被调用的方法抛出异常。
1
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
75
|
// 示例1::
Class<?> clazz = Math.class;
try {
// clazz.getDeclaredMethod("sqrt", double.class):获取 clazz 类中名为 sqrt 且参数类型为 double 的方法。
Method method = clazz.getDeclaredMethod("sqrt", double.class);
method.setAccessible(true); // 允许访问私有方法
// method.invoke(null, 25.0):调用 method 方法,传递参数 25.0。
// 因为 sqrt 是 Math 类的静态方法,所以传递 null 作为第一个参数。
// method.invoke 返回一个 Object 类型的对象,需要将其强制转换为 Double 类型。
double result = (Double) method.invoke(null, 25.0);
System.out.println(result); // 输出 5.0
} catch (Exception e) {
e.printStackTrace();
}
// 示例2:调用实例方法
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void sayHello(String message) {
System.out.println(name + " says: " + message);
}
}
public class ReflectionInstanceMethodExample {
public static void main(String[] args) {
try {
// 创建 Person 对象
Person person = new Person("Alice");
// 获取 Person 类的 Class 对象
Class<?> clazz = person.getClass();
// 获取名为 "sayHello" 且参数类型为 String 的方法
Method method = clazz.getDeclaredMethod("sayHello", String.class);
// 调用 sayHello 方法,传递参数 "Hello, World!"
method.invoke(person, "Hello, World!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 示例 3:处理异常
public class ReflectionExceptionExample {
public static void main(String[] args) {
try {
// 获取 Math 类的 Class 对象
Class<?> clazz = Math.class;
// 获取名为 "sqrt" 且参数类型为 double 的方法
Method method = clazz.getDeclaredMethod("sqrt", double.class);
// 调用 Math.sqrt 方法,传递参数 25.0
Object result = method.invoke(null, 25.0);
// 将结果强制转换为 Double 类型并打印
double sqrtResult = (Double) result;
System.out.println("Square root of 25.0 is: " + sqrtResult);
} catch (NoSuchMethodException e) {
System.out.println("Method not found: " + e.getMessage());
} catch (IllegalAccessException e) {
System.out.println("Illegal access: " + e.getMessage());
} catch (InvocationTargetException e) {
System.out.println("Invocation target exception: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
4. 操作字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Person {
private String name = "Alice";
}
public class Main {
public static void main(String[] args) {
try {
Person person = new Person();
Field field = Person.class.getDeclaredField("name");
field.setAccessible(true); // 允许访问私有字段
field.set(person, "Bob"); // 修改字段值
System.out.println(field.get(person)); // 输出 Bob
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
5. 注意事项
- 性能问题:反射操作比直接调用慢,需避免在频繁执行的代码中使用。
- 安全限制:可能触发安全管理器的权限检查(如修改私有字段需
setAccessible(true)
)。
- 破坏封装性:滥用反射可能导致代码难以维护。
- 版本兼容性:反射依赖类的结构,类结构变化可能导致反射代码失效。
二、序列化(Serialization)
1. 概念
序列化是将对象转换为字节流的过程(用于网络传输或持久化存储),反序列化则是将字节流恢复为对象。
2. 核心接口与类
Serializable
:标记接口,表示类可序列化。
ObjectOutputStream
:序列化输出流。
ObjectInputStream
:反序列化输入流。
transient
:关键字,标记字段不参与序列化。
3. 使用方法
1. 基本序列化
1
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
|
import java.io.*;
class Student implements Serializable {
private static final long serialVersionUID = 1L; // 显式定义版本号
private String name;
private transient int age; // 该字段不序列化
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
Student student = new Student("Alice", 20);
oos.writeObject(student);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.dat"))) {
Student student = (Student) ois.readObject();
System.out.println(student.getName()); // Alice
System.out.println(student.getAge()); // 0(因为 age 是 transient)
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
2. 自定义序列化
通过实现 writeObject
和 readObject
方法控制序列化过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class CustomSerialization implements Serializable {
private String data;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // 默认序列化
oos.writeObject(data.toUpperCase()); // 自定义处理
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化
data = ((String) ois.readObject()).toLowerCase();
}
}
|
3. 版本兼容性
- 修改类后需保证
serialVersionUID
一致,否则反序列化失败。
- 新增字段时,反序列化会赋予默认值(如
null
或 0
)。
4. 注意事项
- 安全风险:反序列化不可信数据可能导致代码执行漏洞(如通过
readObject
触发恶意逻辑)。
- 性能问题:序列化生成的字节流较大,可考虑 JSON/XML 或 Protobuf 等替代方案。
- transient 字段:敏感数据(如密码)应标记为
transient
。
- 静态字段:静态字段不会被序列化。
- Externalizable 接口:更细粒度控制序列化,但需手动实现所有逻辑。
三、总结
反射:
- 适用场景:框架开发(如 Spring 的依赖注入)、动态代理、注解处理器。
- 替代方案:尽量使用接口或设计模式(如工厂模式)避免反射。
序列化:
- 适用场景:网络传输(如 RPC)、持久化存储(如缓存)。
- 替代方案:使用 JSON(如 Jackson/Gson)或二进制协议(如 Protobuf)。
共同原则:
- 谨慎使用:优先选择更安全的替代方案。
- 防御性编程:处理异常和边界条件。
- 性能优化:缓存反射的元数据(如
Class
对象),避免重复序列化。
通过合理使用反射和序列化,可以增强代码的灵活性,但需在安全性和性能之间找到平衡。