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替代
参数传递 只有值传递 理解引用副本机制
初始化 静态→实例→构造 注意父子类顺序
编码规范 命名、异常处理 遵循团队规范
相关推荐
社恐的下水道蟑螂2 小时前
LangChain:AI 应用开发框架的深度解析与实践指南
前端·langchain·ai编程
这周也會开心2 小时前
Map集合的比较
java·开发语言·jvm
凌览2 小时前
2025年,我和AI合伙开发了四款小工具
前端·javascript·后端
青莲8432 小时前
Java基础篇——第一部
android·前端
留简2 小时前
从零搭建一个现代化后台管理系统:基于 React 19 + Vite + Ant Design Pro 的最佳实践
前端·react.js
while(1){yan}2 小时前
SpringIoc
java·spring boot·spring·java-ee
小满zs2 小时前
Next.js第十八章(静态导出SSG)
前端·next.js
CAN11772 小时前
快速还原设计稿之工作流集成方案
前端·人工智能
A24207349302 小时前
深入浅出JS事件:从基础原理到实战进阶全解析
开发语言·前端·javascript