第八章:序列化与反序列化
8.1 序列化基础
8.1.1 什么是序列化和反序列化?
序列化 :将对象转换为字节流的过程
反序列化:将字节流恢复为对象的过程
8.1.2 Serializable接口
java
arduino
public class User implements Serializable {
private String name;
private int age;
// 必须实现Serializable接口
}
8.1.3 serialVersionUID的作用
作用:版本控制,确保序列化和反序列化的类版本一致
java
java
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 修改类结构时应该更新UID
}
8.1.4 transient关键字
java
typescript
public class User implements Serializable {
private String name;
private transient String password; // 不会被序列化
}
8.1.5 序列化的使用场景
- 网络传输
- 对象持久化
- 远程方法调用(RPC)
- 缓存存储
8.2 序列化机制
8.2.1 默认序列化机制
原理:使用ObjectOutputStream和ObjectInputStream
java
java
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.dat"))) {
oos.writeObject(user);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.dat"))) {
User user = (User) ois.readObject();
}
8.2.2 自定义序列化
java
java
public class User implements Serializable {
private String name;
private transient String sensitiveData;
private void writeObject(ObjectOutputStream oos)
throws IOException {
oos.defaultWriteObject();
// 自定义加密
oos.writeObject(encrypt(sensitiveData));
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// 自定义解密
sensitiveData = decrypt((String) ois.readObject());
}
}
8.2.3 Externalizable接口
与Serializable对比:
- Externalizable需要手动实现序列化逻辑
- 性能更好,控制更细
java
java
public class User implements Externalizable {
private String name;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
}
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
name = in.readUTF();
}
}
8.2.4 序列化的继承关系
规则:
- 父类实现Serializable → 子类自动可序列化
- 父类未实现Serializable → 子类序列化时,父类必须有无参构造器
8.2.5 静态字段和瞬态字段
- 静态字段:不会被序列化(属于类,不属于对象)
- 瞬态字段:使用transient修饰,不会被序列化
8.3 序列化安全性
8.3.1 序列化漏洞原理
问题:反序列化时可以执行任意代码
java
java
public class Malicious implements Serializable {
private void readObject(ObjectInputStream in)
throws Exception {
Runtime.getRuntime().exec("恶意命令");
}
}
8.3.2 反序列化攻击防护
- 验证输入来源
- 使用白名单验证类
- 使用安全过滤器
java
scala
public class SafeObjectInputStream extends ObjectInputStream {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
// 白名单验证
if (!desc.getName().startsWith("com.safe.")) {
throw new InvalidClassException("不安全的类");
}
return super.resolveClass(desc);
}
}
8.3.3 安全的序列化实践
- 使用final serialVersionUID
- 避免序列化敏感数据
- 验证反序列化对象
8.3.4 替代方案
JSON序列化:
java
ini
// 使用Jackson
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
User user2 = mapper.readValue(json, User.class);
Protobuf:
protobuf
ini
// 定义.proto文件
message User {
string name = 1;
int32 age = 2;
}
对比:
| 方案 | 性能 | 安全性 | 可读性 | 跨语言 |
|---|---|---|---|---|
| Java原生 | 中 | 低 | 差 | 否 |
| JSON | 低 | 高 | 好 | 是 |
| Protobuf | 高 | 高 | 差 | 是 |
8.4 序列化性能优化
8.4.1 序列化性能问题
- 序列化/反序列化开销大
- 序列化后数据量大
- 频繁GC压力
8.4.2 序列化框架对比
| 框架 | 性能 | 大小 | 易用性 |
|---|---|---|---|
| Java原生 | 基准 | 基准 | 高 |
| Kryo | 10x+ | 小 | 中 |
| Hessian | 2x | 中 | 高 |
| Protobuf | 5x | 小 | 低 |
8.4.3 序列化大小优化
- 使用transient排除不必要字段
- 使用基本类型而非包装类
- 压缩序列化数据
java
java
public class OptimizedUser implements Serializable {
private transient int tempField; // 不序列化
private int age; // 使用基本类型
private String name;
}
8.4.4 序列化版本兼容性
兼容性策略:
- 只添加新字段 → 向前兼容
- 不删除字段,只标记为废弃
- 修改serialVersionUID时明确版本变更
第十章:其他重要概念
10.1 Java中的值传递和引用传递
10.1.1 Java参数传递机制
核心概念:Java中只有值传递,没有引用传递
10.1.2 基本类型参数传递
java
arduino
public class Test {
public static void modify(int x) {
x = 10; // 不影响原始值
}
public static void main(String[] args) {
int a = 5;
modify(a);
System.out.println(a); // 输出5
}
}
10.1.3 引用类型参数传递
java
csharp
public class Test {
public static void modifyArray(int[] arr) {
arr[0] = 10; // 修改对象内容,影响原始对象
}
public static void changeReference(int[] arr) {
arr = new int[]{100}; // 改变引用,不影响原始引用
}
public static void main(String[] args) {
int[] array = {1, 2, 3};
modifyArray(array);
System.out.println(array[0]); // 输出10
changeReference(array);
System.out.println(array[0]); // 仍然输出10
}
}
10.1.4 常见的误解澄清
误解 :Java有引用传递
事实:传递的是引用的副本(值传递引用)
10.1.5 实际案例分析
java
ini
class Person {
String name;
Person(String name) { this.name = name; }
}
public class Test {
static void swap(Person a, Person b) {
Person temp = a;
a = b;
b = temp; // 只交换了副本,不影响原始引用
}
public static void main(String[] args) {
Person p1 = new Person("Alice");
Person p2 = new Person("Bob");
swap(p1, p2);
System.out.println(p1.name); // 仍然是"Alice"
}
}
10.2 对象初始化顺序
10.2.1 单个类的初始化顺序
- 静态字段和静态代码块(按代码顺序)
- 实例字段和实例代码块(按代码顺序)
- 构造方法
java
csharp
public class InitOrder {
// 1. 静态字段
static int staticField = initStaticField();
// 2. 静态代码块
static {
System.out.println("静态代码块");
}
// 3. 实例字段
int instanceField = initInstanceField();
// 4. 实例代码块
{
System.out.println("实例代码块");
}
// 5. 构造方法
public InitOrder() {
System.out.println("构造方法");
}
}
10.2.2 父子类的初始化顺序
- 父类静态 → 子类静态
- 父类实例 → 父类构造
- 子类实例 → 子类构造
java
scala
class Parent {
static { System.out.println("Parent静态代码块"); }
{ System.out.println("Parent实例代码块"); }
Parent() { System.out.println("Parent构造方法"); }
}
class Child extends Parent {
static { System.out.println("Child静态代码块"); }
{ System.out.println("Child实例代码块"); }
Child() { System.out.println("Child构造方法"); }
}
10.2.3-10.2.5 总结表格
| 初始化阶段 | 执行时机 | 执行次数 |
|---|---|---|
| 静态成员 | 类加载时 | 1次 |
| 实例成员 | 每次new对象时 | N次 |
| 构造方法 | 对象创建最后 | N次 |
10.3 编码规范与最佳实践
10.3.1 命名规范
- 类名 :大驼峰,
UserService - 方法名 :小驼峰,
getUserName() - 常量 :全大写,
MAX_SIZE - 包名 :全小写,
com.example.util
10.3.2 代码格式
java
typescript
// 好的格式
public class Example {
private String name;
public void doSomething() {
if (condition) {
// 缩进4个空格
methodCall();
}
}
}
10.3.3 注释规范
java
php
/**
* 用户服务类
* @author 作者
* @version 1.0
*/
public class UserService {
/**
* 根据ID获取用户
* @param id 用户ID
* @return 用户对象
* @throws UserNotFoundException 用户不存在时抛出
*/
public User getUserById(int id) {
// 单行注释
return userRepository.findById(id);
}
}
10.3.4 异常处理规范
java
php
// 好的实践
try {
processFile();
} catch (FileNotFoundException e) {
log.error("文件未找到", e);
throw new BusinessException("文件处理失败", e);
} finally {
cleanup();
}
// 不好的实践
try {
// 捕获所有异常
} catch (Exception e) {
// 空的catch块
}
10.3.5 性能优化建议
- 字符串操作:使用StringBuilder拼接
- 集合初始化:指定初始容量
- 避免重复计算:缓存结果
- 使用基本类型:代替包装类
java
ini
// 优化前
String result = "";
for (int i = 0; i < 100; i++) {
result += i; // 每次循环创建新对象
}
// 优化后
StringBuilder sb = new StringBuilder(100);
for (int i = 0; i < 100; i++) {
sb.append(i);
}
String result = sb.toString();
10.4 常见面试题解析
10.4.1 经典面试题汇总
- Java是值传递还是引用传递?
- String为什么是不可变的?
- equals和==的区别?
- final、finally、finalize的区别?
10.4.2 面试题解答思路
STAR原则:
- Situation:问题背景
- Task:需要解决的任务
- Action:采取的行动
- Result:取得的结果
10.4.3 代码分析题示例
java
ini
// 问题:输出结果是什么?
public class Test {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true
}
}
解答要点:
- 字符串常量池
- intern()方法
- 对象引用比较 vs 值比较
10.4.4 设计思路题
题目:设计一个线程安全的单例模式
java
csharp
// 双重检查锁定
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 枚举实现(推荐)
public enum SingletonEnum {
INSTANCE;
public void doSomething() {}
}
设计要点:
- 延迟初始化
- 线程安全
- 防止反射攻击
- 序列化安全
总结对比表
| 概念 | 关键点 | 最佳实践 |
|---|---|---|
| 内部类 | 访问权限、内存泄漏 | 优先使用静态内部类 |
| 枚举 | 类型安全、单例模式 | 替代常量类,实现接口 |
| 序列化 | 安全性、性能 | 使用JSON/Protobuf替代 |
| 参数传递 | 只有值传递 | 理解引用副本机制 |
| 初始化 | 静态→实例→构造 | 注意父子类顺序 |
| 编码规范 | 命名、异常处理 | 遵循团队规范 |