Java序列化案例如何实现对象

wen java案例 32

Java序列化案例:从基础到实践的完整指南

目录导读

  • 什么是Java序列化?为什么需要它?
  • 序列化的核心机制与实现步骤
  • 简单对象序列化与反序列化
  • 自定义序列化与transient关键字
  • 继承与序列化时的注意事项
  • 常见问题与最佳实践
  • 问答环节:解决你最常见的疑惑

什么是Java序列化?为什么需要它?

Java序列化是指将Java对象转换为字节序列(byte stream)的过程,以便存储到文件、数据库或通过网络传输,反序列化则是将字节序列恢复为原始对象的过程。

Java序列化案例如何实现对象

为什么需要序列化?

  • 持久化存储:将对象状态保存到磁盘,以便下次启动时恢复。
  • 远程通信:在分布式系统(如RMI、Socket、EJB)中,通过网络传递对象。
  • 深拷贝:通过序列化实现对象的完整复制,避免引用共享。
  • 缓存:将对象写入Redis、Memcached等缓存系统。

关键点:只有实现了java.io.Serializable接口的类才能被序列化(该接口是一个标记接口,不包含任何方法)。


序列化的核心机制与实现步骤

核心类

  • ObjectOutputStream:将对象写入输出流(序列化)。
  • ObjectInputStream:从输入流读取对象(反序列化)。

标准实现步骤

  1. 使类实现Serializable接口
  2. 创建ObjectOutputStream对象,绑定文件/网络输出流。
  3. 调用writeObject()方法,将对象写入流。
  4. 关闭资源
  5. 反序列化时:使用ObjectInputStream的readObject()读取。

注意:序列化会递归保存对象的所有成员变量(包括引用对象),除非被标记为transient


简单对象序列化与反序列化

代码实现

import java.io.*;
// 1. 实现Serializable接口
class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private double salary;
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee{name='" + name + "', age=" + age + ", salary=" + salary + "}";
    }
}
public class SerializationDemo {
    public static void main(String[] args) {
        String filename = "employee.ser";
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(filename))) {
            Employee emp = new Employee("张三", 30, 5000.0);
            oos.writeObject(emp);
            System.out.println("序列化成功:" + emp);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(filename))) {
            Employee emp = (Employee) ois.readObject();
            System.out.println("反序列化成功:" + emp);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出结果

序列化成功:Employee{name='张三', age=30, salary=5000.0}
反序列化成功:Employee{name='张三', age=30, salary=5000.0}

关键点serialVersionUID用于版本控制,如果类结构发生变化(如增加字段),反序列化会抛出InvalidClassException


自定义序列化与transient关键字

场景:密码不应被序列化

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password; // 不会被序列化
    private int age;
    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }
    // 自定义序列化逻辑:加密密码后才序列化
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        // 这里简单模拟加密:将密码反转
        String encrypted = new StringBuilder(password).reverse().toString();
        oos.writeObject(encrypted);
    }
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        String encrypted = (String) ois.readObject();
        this.password = new StringBuilder(encrypted).reverse().toString();
    }
    @Override
    public String toString() {
        return "User{username='" + username + "', password='" + password + "', age=" + age + "}";
    }
}
public class CustomSerialization {
    public static void main(String[] args) {
        String file = "user.ser";
        User user = new User("admin", "123456", 25);
        System.out.println("原始对象:" + user);
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
            oos.writeObject(user);
        } catch (IOException e) { e.printStackTrace(); }
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            User loaded = (User) ois.readObject();
            System.out.println("反序列化对象:" + loaded);
            System.out.println("密码一致性:" + "123456".equals(loaded.password));
        } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }
    }
}

输出结果

原始对象:User{username='admin', password='123456', age=25}
反序列化对象:User{username='admin', password='123456', age=25}
密码一致性:true

说明

  • transient字段不会被默认序列化,但可通过writeObject/readObject自定义序列化逻辑。
  • 注意:自定义方法必须是private,且名称固定。

继承与序列化时的注意事项

场景:父类未实现Serializable

class Parent {
    String parentField = "parent";
    public Parent() {
        System.out.println("Parent constructor called");
    }
}
class Child extends Parent implements Serializable {
    private static final long serialVersionUID = 1L;
    String childField = "child";
    public Child() {
        System.out.println("Child constructor called");
    }
}
public class InheritanceSerialization {
    public static void main(String[] args) {
        Child child = new Child();
        child.parentField = "modified parent";
        String file = "child.ser";
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
            oos.writeObject(child);
        } catch (IOException e) { e.printStackTrace(); }
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            Child loaded = (Child) ois.readObject();
            System.out.println("parentField: " + loaded.parentField);
            System.out.println("childField: " + loaded.childField);
        } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }
    }
}

输出结果

Parent constructor called
Child constructor called
Parent constructor called  // 反序列化时父类构造器被调用
parentField: parent        // 父类字段被重置为默认值
childField: child

核心规则

  • 如果父类未实现Serializable,则父类字段不会被序列化,反序列化时,父类的无参构造器会被调用,字段恢复为默认值。
  • 如果父类实现了Serializable,则子类自动可序列化,且所有字段都会被保存。

常见问题与最佳实践

什么时候序列化会失败?

  • 类未实现Serializable
  • 类包含了不可序列化的成员(可通过transient解决)。
  • serialVersionUID不匹配。
  • 网络传输过程中流中断。

如何控制序列化内容?

  • 使用transient排除敏感字段。
  • 实现Externalizable接口(需手动实现writeExternalreadExternal,性能更高但更复杂)。

序列化与克隆的关系?

  • 通过序列化实现深拷贝:将对象序列化到字节数组,再反序列化回来。

序列化版本号(serialVersionUID)的作用?

  • 用于验证反序列化时类的版本是否兼容,建议显式声明,避免JVM自动生成(自动生成依赖于类结构,易变)。

问答环节:解决你最常见的疑惑

Q1:static字段会被序列化吗? A:不会。static字段属于类级别,不属于对象实例,序列化只保存实例状态。

Q2:如果父类实现了Serializable,子类还需要实现吗? A:不需要,子类自动继承序列化能力,但需确保子类中所有成员都可序列化。

Q3:transient关键字能用在哪些地方? A:只能用于实例变量(成员变量),不能用于类变量(static)或局部变量。

Q4:反序列化时构造器会被调用吗? A:对于实现了Serializable的类,不会调用任何构造器,对象通过字节流直接恢复状态,但如果是父类未实现Serializable,则父类的无参构造器会被调用(如上例所示)。

Q5:如何防止反序列化攻击? A:

  • 使用ObjectInputFilter设置白名单/黑名单(Java 9+)。
  • 对敏感字段使用transient
  • 实现readObjectNoData等方法提供默认值。
  • 避免反序列化不可信来源的数据。

Q6:序列化与JSON/XML的区别? A:JSON/XML是文本格式,跨语言、可读性好,但体积较大;Java序列化是二进制格式,依赖Java环境,但能完整保留对象类型和引用关系,实际开发中,RPC框架多使用JSON(如RESTful)或专用二进制协议(如Protobuf)替代Java序列化。


通过以上案例和解析,你应该能掌握Java序列化的核心用法、自定义技巧以及潜在陷阱,在实际项目中,优先选择成熟的序列化框架(如Jackson、Protostuff)以提升性能和安全性,如有更多问题,欢迎在评论区交流!

抱歉,评论功能暂时关闭!