tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础与面向对象

    • Java中的一些概念
    • Java中的数组
    • String类跟关键字final
    • Java 日期时间与Date
    • Java中的异常处理
    • Java枚举
    • Java序列化和反序列化
      • 什么是序列化和反序列化
      • 使用场景
      • 实现序列化
        • 实现Serializable 接口
        • 实现 Externalizable 接口
        • 使用 JSON 序列化库(如 Jackson、Gson)
        • static 和 transient
      • 扩展
        • DTO之类的类,必须实现Serializable接口?
        • serialVersionUID字段
        • 转为json字符串替代实现Serializable
    • Java中的注解
    • Java中的IO流
    • Java中抽象类与接口
  • 高级进阶

  • 并发合集

  • JVM合集

  • 实战与细节

  • 代码之丑与提升

  • 《Java》学习笔记
  • Java基础与面向对象
EffectTang
2024-11-04
目录

Java序列化和反序列化

# Java序列化和反序列化

# 什么是序列化和反序列化

序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。在Java中,这通常意味着将对象转换为字节序列。

反序列化则是序列化的逆过程,它将字节序列恢复为对象。

在日常开发中,将对象转换为JSON字符串的过程确实可以视为序列化的一种形式。序列化是指将对象的状态信息转换为可以存储或传输的格式,而JSON字符串是一种常见的序列化格式,特别适合跨语言和平台的数据交换。

JSON 字符串转为对象则是反序列化。

# 使用场景

我们日常开发中,序列化和反序列化其实还是挺常见的,只不过有时候我们忘记了自己所做的事情其实就是序列化和反序列化。

举几个常见的场景:

  1. 网络传输:在分布式系统中,对象需要在网络上传输时,需要将对象序列化后发送,接收方再进行反序列化。
  2. 数据存储:将对象状态保存到文件或数据库中,以便后续恢复。
  3. 远程方法调用(RMI):在 Java 的 RMI 中,对象需要在客户端和服务器之间传递。
  4. 对象克隆:通过序列化和反序列化实现对象的深拷贝。

类似的场景其实很多。

不过就日常开发而言,可能大家在从 Redis 中存取对象、Dubbo 远程调用,这些场景可能会明确感知到序列化这件事,其他场景可能感受就不是特别明显。

# 实现序列化

# 实现Serializable 接口

假设有一个Student类,需要将其对象序列化到文件中。

import java.io.Serializable;  
import java.io.FileOutputStream;  
import java.io.ObjectOutputStream;  
  
public class Student implements Serializable {  
    private static final long serialVersionUID = 1L; // 用于版本控制  
    private String name;  
    private int age;  
  
    // 构造函数、getter和setter省略  
  
    public static void main(String[] args) {  
        Student student = new Student("张三", 20);  
        try (FileOutputStream fos = new FileOutputStream("student.ser");  
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {  
            oos.writeObject(student);  
            System.out.println("对象序列化成功!");  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 实现 Externalizable 接口

与 Serializable 接口类似,但 Externalizable 接口提供了更灵活的序列化控制。

import java.io.Externalizable;  
import java.io.FileOutputStream;  
import java.io.ObjectOutput;  
import java.io.ObjectOutputStream;  
  
public class Employee implements Externalizable {  
    private String name;  
    private int age;  
  
    // 构造函数、getter和setter省略  
  
    @Override  
    public void writeExternal(ObjectOutput out) throws IOException {  
        out.writeObject(name);  
        out.writeInt(age);  
    }  
  
    @Override  
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
        name = (String) in.readObject();  
        age = in.readInt();  
    }  
   public static void main(String[] args) {  
        // 序列化与反序列化逻辑与Serializable类似,但会调用writeExternal和readExternal方法  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

相比于 Serializable,Externalizable 的灵活性主要体现在四点:

  1. Serializable 接口的序列化过程是由 JVM 自动完成的,不允许开发者对序列化过程进行自定义。Externalizable 接口要求开发者实现 writeExternal() 和 readExternal() 两个方法,从而完全控制序列化过程。这意味着开发者可以决定哪些字段需要序列化,哪些不需要,以及如何序列化这些字段。
  2. Serializable 的自动序列化过程虽然方便,但可能不是最高效的。Externalizable 允许开发者自定义序列化过程,因此可以针对特定需求进行优化。例如,可以只序列化必要的字段,或者采用更高效的数据结构来存储序列化数据,从而提高性能。
  3. Serializable 在默认情况下会序列化对象的所有非 transient 字段。如果对象的类结构发生变化如添加或删除字段,则可能会影响序列化和反序列化的兼容性。而对于 Externalizable 接口,开发者可以精确控制哪些字段被序列化,从而更容易地管理版本兼容性问题,甚至还可以在 writeExternal() 和 readExternal() 方法中添加逻辑来处理不同版本的序列化数据。
  4. 由于 Serializable 的序列化过程是自动的,因此可能会无意中序列化敏感信息(如密码、密钥等),此外,恶意用户还可能通过修改序列化数据来攻击系统。而 Externalizable 接口允许开发者明确控制哪些信息被序列化,从而可以减少敏感信息被泄露的风险,开发者甚至还可以在序列化过程中添加额外的安全措施(如加密、签名等)来提高系统的安全性。

# 使用 JSON 序列化库(如 Jackson、Gson)

import com.fasterxml.jackson.databind.ObjectMapper;  
  
public class User {  
    private String username;  
    private int age;  
  
    // 构造函数、getter和setter省略  
  
    public static void main(String[] args) {  
        try {  
            User user = new User("李四", 30);  
            ObjectMapper mapper = new ObjectMapper();  
            String json = mapper.writeValueAsString(user);  
            System.out.println(json); // 输出JSON字符串  
  
            // 反序列化  
            User deserializedUser = mapper.readValue(json, User.class);  
            System.out.println(deserializedUser.getUsername()); // 输出:李四  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# static 和 transient

static 和 transient 是序列化时两个比较特殊的字段。

  • static:static 字段是类级别的,不属于对象实例,因此在序列化时不会被包含。
  • transient:transient 关键字修饰的字段在序列化过程中会被忽略,不会被序列化。

# 扩展

# DTO之类的类,必须实现Serializable接口?

虽然DTO类并不强制要求实现Serializable接口,但在许多场景中实现Serializable接口是非常有用的。它可以帮助你在远程调用、对象持久化、会话管理和框架集成等场景中更方便地处理对象。

实现Serializable接口可以使代码更加一致和可维护,为未来的扩展打下良好的基础。

假设我们有一个UserDTO类,用于在客户端和服务端之间传递用户数据。

import java.io.Serializable;

public class UserDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private String email;
    private int age;

    public UserDTO(String username, String email, int age) {
        this.username = username;
        this.email = email;
        this.age = age;
    }

    // Getters and Setters
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "UserDTO{" +
                "username='" + username + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

关于**serialVersionUID**字段:

  • 建议为每个实现Serializable接口的类提供一个serialVersionUID,以确保在反序列化时类的版本一致性。
  • serialVersionUID是一个长整型值,可以自动生成或手动指定。

# serialVersionUID字段

serialVersionUID 是一个用于标识序列化类的版本号。如果你不显式地为一个实现了 Serializable 接口的类指定 serialVersionUID,Java 会在编译时自动生成一个默认的 serialVersionUID。然而,不显式指定 serialVersionUID 可能会导致一些潜在的问题和不便。以下是一些主要的影响和问题:

默认生成的 serialVersionUID 不稳定

Java 会根据类的结构(包括类名、方法签名、字段等)自动生成 serialVersionUID。这意味着如果你对类的结构进行了任何修改(如添加、删除或修改字段、方法等),生成的 serialVersionUID 也会发生变化。这可能导致以下问题:

  • 反序列化失败:如果你在不同的环境中(如开发、测试、生产)对类进行了修改,生成的 serialVersionUID 不同,尝试从旧版本的序列化数据中反序列化新版本的对象时,可能会抛出 InvalidClassException 异常。

  • 兼容性问题:不同版本的类之间的兼容性问题,尤其是在分布式系统中,不同节点可能使用不同版本的类。

  • 调试困难:默认生成的 serialVersionUID 使得调试反序列化错误更加困难。

  • 性能影响:虽然影响很小,但显式指定 serialVersionUID 可以减少不必要的计算开销。

  • 代码的一致性和可维护性:显式指定 serialVersionUID 使代码更加一致和可维护。

因此,为了确保序列化的稳定性和兼容性,建议在实现 Serializable 接口的类中显式指定 serialVersionUID。

# 转为json字符串替代实现Serializable

当你将一个类转换为JSON字符串进行传输时,实际上已经完成了一种序列化过程。JSON字符串是一种文本格式,可以很容易地在网络上传输和存储。因此,这种情况下通常不需要实现 Serializable 接口,因为 Serializable 接口主要用于二进制序列化,而不是文本格式的序列化。

为什么不需要实现 Serializable 接口?

  1. JSON序列化:
    • JSON序列化将对象转换为JSON字符串,这是一种文本格式,可以直接在网络上传输。
    • 常用的JSON库(如Gson、Jackson、FastJSON)提供了方便的方法来将对象转换为JSON字符串和从JSON字符串转换回对象。
  2. 传输和存储:
    • JSON字符串可以直接存储在文件中、数据库中,或者通过HTTP请求在网络上传输。
    • 由于JSON是一种广泛支持的格式,可以很容易地在不同的编程语言和平台之间进行数据交换。
上次更新: 2025/04/23, 16:23:16
Java枚举
Java中的注解

← Java枚举 Java中的注解→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式