Java 中的序列化与反序列化:原理、使用场景及异常处理详解

序列化与反序列化的基本原理

序列化 是将对象的状态转换为字节流的过程,以便保存到文件、数据库或通过网络传输。Java 提供了 java.io.Serializable 接口来实现对象的序列化。任何需要序列化的类都必须实现这个接口。

反序列化 是将字节流转换回对象的过程,恢复对象的状态。Java 提供了 ObjectInputStream 类来读取序列化的对象,并将其转换回原始对象。

序列化的基本步骤
  1. 实现 Serializable 接口 :需要序列化的类必须实现 Serializable 接口。
  2. 使用 ObjectOutputStream:将对象写入输出流。
  3. 使用 ObjectInputStream:从输入流中读取对象。

代码示例

以下代码展示了如何将一个 User 对象序列化到文件中,并从文件中反序列化回来。

复制代码
package chapter08;

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        File dataFile = new File("user.dat");

        // 序列化
        try (ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream(dataFile))) {
            User user = new User("Alice", 25);
            objectOut.writeObject(user);
            System.out.println("序列化后的对象:" + user);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream objectIn = new ObjectInputStream(new FileInputStream(dataFile))) {
            User user = (User) objectIn.readObject();
            System.out.println("反序列化后的对象:" + user);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + '}';
    }
}
运行结果
复制代码
序列化后的对象:User{name='Alice', age=25}
反序列化后的对象:User{name='Alice', age=25}

异常处理

在进行文件操作和对象序列化时,可能会遇到以下几种异常:

  1. FileNotFoundException:文件未找到异常,通常在尝试打开一个不存在的文件时发生。
  2. IOException:输入输出异常,通常在读写过程中发生。
  3. ClassNotFoundException:类未找到异常,通常在反序列化时发生,表明序列化流中的类在当前环境中不可用。
  4. NotSerializableException :对象未实现 Serializable 接口,无法进行序列化。
异常处理示例
复制代码
package chapter08;

import java.io.*;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        FileInputStream in = null;
        ObjectInputStream objIn = null;
        ObjectOutputStream objOut = null;

        try {
            in = new FileInputStream("nonexistentfile.txt");

            objOut = new ObjectOutputStream(new FileOutputStream("user.dat"));
            objOut.writeObject(new User("Bob", 30));

            objIn = new ObjectInputStream(new FileInputStream("user.dat"));
            User user = (User) objIn.readObject();
            System.out.println("读取的对象:" + user);

        } catch (FileNotFoundException e) {
            System.err.println("文件未找到:" + e.getMessage());
        } catch (IOException e) {
            System.err.println("输入输出异常:" + e.getMessage());
        } catch (ClassNotFoundException e) {
            System.err.println("类未找到:" + e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    System.err.println("关闭输入流异常:" + e.getMessage());
                }
            }

            if (objIn != null) {
                try {
                    objIn.close();
                } catch (IOException e) {
                    System.err.println("关闭对象输入流异常:" + e.getMessage());
                }
            }

            if (objOut != null) {
                try {
                    objOut.close();
                } catch (IOException e) {
                    System.err.println("关闭对象输出流异常:" + e.getMessage());
                }
            }
        }
    }
}

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + '}';
    }
}
运行结果
复制代码
文件未找到:nonexistentfile.txt (系统找不到指定的文件。)
读取的对象:User{name='Bob', age=30}

自定义序列化

有时我们需要自定义序列化过程,Java 提供了 writeObjectreadObject 方法来实现这一点。

复制代码
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age; // 不希望序列化的字段使用 transient 关键字

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 默认序列化
        out.writeInt(age); // 自定义序列化
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 默认反序列化
        age = in.readInt(); // 自定义反序列化
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + '}';
    }
}

版本控制

序列化的类应定义一个唯一的序列版本号 serialVersionUID,以确保在反序列化时版本的兼容性。

复制代码
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    // 构造函数、getter、setter 和其他方法
}

使用场景

  1. 持久化存储:将对象保存到文件或数据库中。
  2. 网络传输:在分布式系统中传输对象。
  3. 深拷贝:通过序列化和反序列化实现对象的深拷贝。
实际应用中的注意事项
  1. 安全性:序列化和反序列化可能会带来安全风险,特别是在反序列化不受信任的数据时,可能会引发反序列化漏洞。
  2. 性能:序列化和反序列化的性能可能成为瓶颈,特别是在处理大量数据时。
  3. 兼容性:确保不同版本的类在序列化和反序列化时的兼容性。

总结

  1. 序列化和反序列化:用于将对象转换为字节流以便存储或传输,然后再将字节流转换回对象。
  2. 自定义序列化 :通过实现 writeObjectreadObject 方法来自定义序列化过程。
  3. 版本控制 :使用 serialVersionUID 确保类的版本兼容性。
  4. 异常处理和资源管理:在进行文件操作和对象序列化时,必须处理可能出现的异常,并确保所有打开的流都被正确关闭。
相关推荐
刚入门的大一新生2 分钟前
C++初阶-string类的模拟实现与改进
开发语言·c++
chxii1 小时前
5java集合框架
java·开发语言
老衲有点帅2 小时前
C#多线程Thread
开发语言·c#
C++ 老炮儿的技术栈2 小时前
什么是函数重载?为什么 C 不支持函数重载,而 C++能支持函数重载?
c语言·开发语言·c++·qt·算法
IsPrisoner2 小时前
Go语言安装proto并且使用gRPC服务(2025最新WINDOWS系统)
开发语言·后端·golang
Python私教2 小时前
征服Rust:从零到独立开发的实战进阶
服务器·开发语言·rust
chicpopoo2 小时前
Python打卡DAY25
开发语言·python
yychen_java2 小时前
R-tree详解
java·算法·r-tree
JANYI20183 小时前
嵌入式设计模式基础--C语言的继承封装与多态
java·c语言·设计模式