背景

本文是《Java 后端从小白到大神》修仙系列第十六篇,正式进入Java后端世界,本篇文章主要聊Java基础。若想详细学习请点击首篇博文,我们开始把。

文章概览

  1. 反射与序列化

反射与序列化

一、反射(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. 注意事项

  1. 性能问题:反射操作比直接调用慢,需避免在频繁执行的代码中使用。
  2. 安全限制:可能触发安全管理器的权限检查(如修改私有字段需 setAccessible(true))。
  3. 破坏封装性:滥用反射可能导致代码难以维护。
  4. 版本兼容性:反射依赖类的结构,类结构变化可能导致反射代码失效。

二、序列化(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. 自定义序列化

通过实现 writeObjectreadObject 方法控制序列化过程:

 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 一致,否则反序列化失败。
  • 新增字段时,反序列化会赋予默认值(如 null0)。

4. 注意事项

  1. 安全风险:反序列化不可信数据可能导致代码执行漏洞(如通过 readObject 触发恶意逻辑)。
  2. 性能问题:序列化生成的字节流较大,可考虑 JSON/XML 或 Protobuf 等替代方案。
  3. transient 字段:敏感数据(如密码)应标记为 transient
  4. 静态字段:静态字段不会被序列化。
  5. Externalizable 接口:更细粒度控制序列化,但需手动实现所有逻辑。

三、总结

反射

  • 适用场景:框架开发(如 Spring 的依赖注入)、动态代理、注解处理器。
  • 替代方案:尽量使用接口或设计模式(如工厂模式)避免反射。

序列化

  • 适用场景:网络传输(如 RPC)、持久化存储(如缓存)。
  • 替代方案:使用 JSON(如 Jackson/Gson)或二进制协议(如 Protobuf)。

共同原则

  • 谨慎使用:优先选择更安全的替代方案。
  • 防御性编程:处理异常和边界条件。
  • 性能优化:缓存反射的元数据(如 Class 对象),避免重复序列化。

通过合理使用反射和序列化,可以增强代码的灵活性,但需在安全性和性能之间找到平衡。