“避免序列化灾难:掌握实现 Serializable 的真相!(二)”

ops/2024/10/24 1:15:42/

文章目录

      • 一、什么是序列化?
      • 二、`Serializable` 是如何起作用的?
      • 三、为什么不自动序列化所有对象?
      • 四、Java 序列化的底层原理
        • 序列化的核心步骤:
      • 五、反序列化的原理
      • 六、总结:为什么必须实现 `Serializable` 才能序列化?
      • 推荐阅读文章

在学习 Java 的时候,你可能遇到过一个疑问: 为什么类必须实现 Serializable,否则就没法序列化对象? 这个看似简单的要求,其实和 Java 的底层机制紧密相关。今天,我们今天就来聊聊这个问题的本质,带你了解为什么 Serializable 是 Java 序列化必不可少的一环。

一、什么是序列化?

先来简单回顾一下什么是序列化。序列化的作用,就是把一个Java 对象转换成字节流,这样我们就可以把它存到文件、通过网络传输、或者传递给其他系统。反过来,把字节流还原成对象的过程叫“反序列化”。

例如,你有一个 User 对象,包含用户名和年龄。序列化后,这个对象会变成一串字节:

User user = new User("Alice", 25);
// 序列化对象到文件
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"));
out.writeObject(user);
out.close();

之后你可以读取文件,反序列化回一个 User 对象:

ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"));
User user = (User) in.readObject();
in.close();

这个过程依赖的是 Java 的序列化机制,而 Serializable 就是这个机制的核心。

二、Serializable 是如何起作用的?

Serializable 是一个标记接口,它本身没有定义任何方法。那它为什么如此重要?让我们从 Java 的序列化机制来看。

Java 序列化机制背后是由 ObjectOutputStreamObjectInputStream 这两个类来完成的。当你调用 writeObject() 方法时,Java 会执行一系列操作来把对象转换为字节流。而这些操作,都是基于对象的类是否实现了 Serializable 接口。如果类没有实现 Serializable,Java 会直接抛出异常,阻止序列化的进行。

为什么会这样呢?这是因为 Java 底层的序列化机制需要知道一个类是否允许被序列化。Serializable 就像是给 Java 的一个“信号”,告诉它这个类的对象是可以被安全地序列化和反序列化的。如果类没有实现 Serializable,Java 默认认为这个类的对象不能被序列化,也就没有办法执行后续操作。

三、为什么不自动序列化所有对象?

你可能会想:为什么 Java 不让所有的类自动支持序列化呢?这样岂不是更方便?

实际上,Java 不自动序列化所有对象,是出于安全性和效率的考虑。原因如下:

  1. 安全性问题
    并不是所有类的对象都适合被序列化。有些类可能包含敏感信息,比如密码、系统配置、用户身份信息等。如果随便把这些类序列化,可能会带来安全隐患。例如,一些类设计时可能没有考虑到数据暴露的问题,而序列化可能会无意间将这些数据泄露给外部系统。

  2. 效率问题
    序列化和反序列化的过程是需要消耗系统资源的,特别是在涉及到复杂对象时。如果 Java 默认让所有类都支持序列化,不仅会增加不必要的开销,还可能导致性能下降。比如某些类可能有非常大的对象图,序列化这些对象可能会产生很大的字节流,影响系统效率。

因此,Java 让开发者自己决定哪些类需要支持序列化,而不是一刀切地自动支持所有类。通过让类实现 Serializable,开发者明确地告诉 Java:这个类是可以被序列化的

四、Java 序列化的底层原理

当一个对象实现了 Serializable 接口,Java 底层会使用反射机制来遍历对象的所有字段,并将这些字段的值按顺序转换成字节流。这其中包括:

  • 基本类型(如 intdouble 等)的直接序列化。
  • 对象类型的递归序列化(比如一个对象里包含另一个对象,也会序列化这个嵌套的对象)。

Java 在序列化时,不仅仅是保存对象的字段值,还会保存一些元信息,包括:

  • 类的名称
  • 类的版本号(serialVersionUID
  • 对象的字段及其类型

这些信息使得在反序列化时,Java 能够找到正确的类,并根据保存的字段值恢复出对象。如果类发生了变化(比如字段修改了),还可以通过 serialVersionUID 来判断序列化和反序列化是否兼容。

序列化的核心步骤:
  1. 判断对象是否实现 Serializable:只有实现了这个接口的对象,才能继续序列化。
  2. 遍历对象的字段:使用反射,Java 会依次获取对象的每个字段,并将其转换成字节流。
  3. 保存对象的元数据:保存类的信息、版本号、以及字段的类型和值。
  4. 生成字节流:最终将这些信息“打包”成字节流,便于存储或传输。

五、反序列化的原理

反序列化的过程则是将字节流重新解析成对象。Java 会根据字节流中的元数据,找到对应的类,并使用反射机制重建对象的实例。这个过程需要类的 serialVersionUID 与字节流中的版本号一致,确保类结构没有发生不兼容的变化。

如果对象的类没有实现 Serializable,反序列化时也会失败,因为 Java 没有办法恢复出一个未标记为可序列化的类的对象。

六、总结:为什么必须实现 Serializable 才能序列化?

  • Serializable 是一个标记,告诉 Java 这个类可以安全地被序列化和反序列化。Java 需要这个信号来决定是否执行序列化操作。
  • 安全性:并不是所有对象都适合被序列化,Serializable 让开发者有选择地开放序列化权限,避免敏感数据泄露。
  • 效率:序列化和反序列化是有成本的,不自动支持所有类序列化可以避免不必要的性能开销。
  • 底层机制:Java 使用 Serializable 接口配合反射来实现序列化和反序列化过程,只有实现了 Serializable 的类,Java 才会执行序列化流程。

因此,Java 之所以要求类实现 Serializable,是为了通过这一标记来确保序列化的安全性、性能,以及在底层能够正确执行序列化过程。这也是为什么我们必须显式地让类实现 Serializable,否则 Java 序列化机制就没法发挥作用。

推荐阅读文章

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程
  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
  • 如何理解应用 Java 多线程与并发编程?
  • Java Spring 中常用的 @PostConstruct 注解使用总结
  • 线程 vs 虚拟线程:深入理解及区别
  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
  • Java 中消除 If-else 技巧总结
  • 线程池的核心参数配置(仅供参考)
  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)
  • Java 枚举的几个常用技巧,你可以试着用用
  • 如何理解线程安全这个概念?
  • 理解 Java 桥接方法
  • Spring 整合嵌入式 Tomcat 容器
  • Tomcat 如何加载 SpringMVC 组件

http://www.ppmy.cn/ops/127965.html

相关文章

ARM嵌入式学习--第四天

汇编与C混合编程 -汇编指令中调用C语言 .global _start _start:mov r0,#5mov r1,#3bl add stop:b stop int add(int a,int b) {int c a b;return c; } 无优化情况:(反汇编之后,发现多了很多很多指令,运行之后结果是错误的&a…

k8s部署Kafka集群超详细讲解

准备部署环境 Kubernetes集群信息 NAMEVERSIONk8s-masterv1.29.2k8s-node01v1.29.2k8s-node02v1.29.2 Kafka:3.7.1版本,apche版本 Zookeeper:3.6.3版本 准备StorageClass # kubectl get sc NAME PROVISIONER RECLA…

旅游攻略网站毕业设计计算机毕设基于SpringBootSSM框架

目录 1.摘要 2 引言 2.1 开发目标 2.2 项目内容 2.3 项目背景与目的 3. 技术选型 3.1 JAVA 简介 3.2 MySQL 介绍 ‌4. 功能描述与创新点 4.1 功能描述 ‌4.2创新点设计 4.3 功能图展示 5. 数据库设计 6. 项目任务与要求 1.摘要 本文旨在设计并实现一个功能全面、…

C++代码操作指令的定义

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、最简单的函数二、栈设计StackEntry 类std::vector 容器std::vector<StackEntry>的特点std::vector<StackEntry>的操作成员枚举 Type&#xff1…

RabbitMQ service is already present - only updating service parameters

Windows下卸载RabbitMQ之后,然后重新注册RabbitMQ服务的时候,报错以下信息: D:\software\rabbitmq-server-4.0.2\rabbitmq_server-4.0.2\sbin>D:\software\rabbitmq-server-4.0.2\rabbitmq_server-4.0.2\sbin\rabbitmq-service.bat install RabbitMQ service is already …

【Jmeter】jmeter运行时自动获取线程号几种方式

背景&#xff1a; 在模拟多线程场景时&#xff0c;使用序号可以标记并追踪当前执行请求的虚拟用户。 方法1&#xff1a;通过添加BeanShell前置处理器获取或BeanShell预处理程序获取线程号 获取函数&#xff1a;${__BeanShell(ctx.getThread().getThreadName())} Thread.slee…

②PROFINET转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 PROFINET 转 Modbus TCP &#xff08;接上一章&#xff09; 配置使用 与 PROFINET 主站进行组态说明 这里介绍与西门子 PLC 的…

Godot中类和静态类型

目录 类 关键字class_name 除了为类定义方法&#xff0c;我们也可以为类定义属性字段 实例释放前后的打印 Refcounted RefCounted维护了一个引用计数器 get_reference_count 类是引用类型数据 class关键字 静态类型 静态方法 静态方法只能访问静态变量 类 是面向…