Java基础篇——第三部

第八章:序列化与反序列化

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 序列化的使用场景

  1. 网络传输
  2. 对象持久化
  3. 远程方法调用(RPC)
  4. 缓存存储

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 序列化的继承关系

规则

  1. 父类实现Serializable → 子类自动可序列化
  2. 父类未实现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 反序列化攻击防护

  1. 验证输入来源
  2. 使用白名单验证类
  3. 使用安全过滤器

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 安全的序列化实践

  1. 使用final serialVersionUID
  2. 避免序列化敏感数据
  3. 验证反序列化对象

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 序列化性能问题

  1. 序列化/反序列化开销大
  2. 序列化后数据量大
  3. 频繁GC压力

8.4.2 序列化框架对比

框架 性能 大小 易用性
Java原生 基准 基准
Kryo 10x+
Hessian 2x
Protobuf 5x

8.4.3 序列化大小优化

  1. 使用transient排除不必要字段
  2. 使用基本类型而非包装类
  3. 压缩序列化数据

java

java 复制代码
public class OptimizedUser implements Serializable {
    private transient int tempField;  // 不序列化
    private int age;                  // 使用基本类型
    private String name;
}

8.4.4 序列化版本兼容性

兼容性策略

  1. 只添加新字段 → 向前兼容
  2. 不删除字段,只标记为废弃
  3. 修改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 单个类的初始化顺序

  1. 静态字段和静态代码块(按代码顺序)
  2. 实例字段和实例代码块(按代码顺序)
  3. 构造方法

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 父子类的初始化顺序

  1. 父类静态 → 子类静态
  2. 父类实例 → 父类构造
  3. 子类实例 → 子类构造

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 性能优化建议

  1. 字符串操作:使用StringBuilder拼接
  2. 集合初始化:指定初始容量
  3. 避免重复计算:缓存结果
  4. 使用基本类型:代替包装类

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 经典面试题汇总

  1. Java是值传递还是引用传递?
  2. String为什么是不可变的?
  3. equals和==的区别?
  4. 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() {}
}

设计要点

  1. 延迟初始化
  2. 线程安全
  3. 防止反射攻击
  4. 序列化安全

总结对比表

概念 关键点 最佳实践
内部类 访问权限、内存泄漏 优先使用静态内部类
枚举 类型安全、单例模式 替代常量类,实现接口
序列化 安全性、性能 使用JSON/Protobuf替代
参数传递 只有值传递 理解引用副本机制
初始化 静态→实例→构造 注意父子类顺序
编码规范 命名、异常处理 遵循团队规范
相关推荐
寻星探路6 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
曹牧8 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法9 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7259 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎9 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven