0

0

java怎样实现对象的序列化与反序列化 java序列化操作的基础方法

雪夜

雪夜

发布时间:2025-08-19 19:53:01

|

812人浏览过

|

来源于php中文网

原创

java中实现对象序列化与反序列化的核心是通过实现serializable接口将对象转换为字节流并恢复,其中被transient和static修饰的字段以及父类未实现serializable时的非静态字段不会被序列化,因此在序列化过程中这些字段的状态不会被保存或恢复,从而确保敏感信息不被持久化、共享状态不被重复记录,并正确处理继承关系下的对象重建,最终保证序列化机制的安全性与一致性。

java怎样实现对象的序列化与反序列化 java序列化操作的基础方法

Java中实现对象的序列化与反序列化,核心在于将一个Java对象的状态转换为字节流,以便存储或在网络中传输,之后再将字节流恢复为对象。这听起来有点像魔法,但实际上是Java I/O体系里一个非常基础且强大的功能。简单来说,就是把活生生的对象“冻结”成数据,需要时再“解冻”复活。

解决方案

要让一个Java对象具备序列化能力,最直接的方法就是让它的类实现

java.io.Serializable
接口。这个接口是个标记接口,它本身没有任何方法需要实现,仅仅是告诉JVM:“嘿,我这个类的对象可以被序列化了!”。

序列化过程主要用到

java.io.ObjectOutputStream
,它能将对象写入一个输出流(比如文件流或网络流)。反序列化则使用
java.io.ObjectInputStream
,从输入流中读取字节并重构对象。

立即学习Java免费学习笔记(深入)”;

我们来看一个基础的例子:

import java.io.*;

// 1. 定义一个可序列化的类
class User implements Serializable {
    private static final long serialVersionUID = 1L; // 推荐显式定义
    private String name;
    private int age;
    private transient String password; // 标记为transient的字段不会被序列化

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

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

    // 省略getter/setter
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getPassword() { return password; }
}

public class SerializationDemo {
    public static void main(String[] args) {
        User user = new User("张三", 30, "mySecretPass");
        String filePath = "user.ser"; // 序列化文件路径

        // 序列化
        try (FileOutputStream fileOut = new FileOutputStream(filePath);
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(user);
            System.out.println("User对象已序列化到 " + filePath);
            System.out.println("原始对象: " + user);
        } catch (IOException i) {
            i.printStackTrace();
        }

        // 反序列化
        User deserializedUser = null;
        try (FileInputStream fileIn = new FileInputStream(filePath);
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            deserializedUser = (User) in.readObject();
            System.out.println("User对象已从 " + filePath + " 反序列化。");
            System.out.println("反序列化对象: " + deserializedUser);
            // 注意:transient字段的值为默认值
            System.out.println("反序列化后密码: " + deserializedUser.getPassword()); // 会是null
        } catch (IOException i) {
            i.printStackTrace();
        } catch (ClassNotFoundException c) {
            System.out.println("User class not found");
            c.printStackTrace();
        }
    }
}

运行这段代码,你会发现

password
字段在反序列化后变成了
null
,这正是
transient
关键字的作用。

Java对象序列化时,哪些字段会被忽略?

这问题问得挺好,因为不是所有字段都会乖乖地被序列化。在Java的默认序列化机制中,有几种情况下的字段是会被“跳过”的:

首先,也是最常见的,就是被

transient
关键字修饰的字段。
transient
直译过来是“瞬态的”、“临时的”,用它来标记一个字段,就等于告诉JVM:“这个字段我不想让它参与序列化过程,它的值不应该被保存下来。” 为什么会有这种需求呢?可能是这个字段存储的是敏感信息(比如密码),不希望它以明文形式出现在序列化数据中;也可能是这个字段是运行时动态计算出来的,或者它引用了一个不能被序列化(或不应该被序列化)的对象,比如一个线程、一个文件句柄,或者一个数据库连接。序列化后,这些
transient
字段的值会被恢复成其类型的默认值(对象类型为
null
,基本类型为0、
false
等)。

其次,

static
(静态)字段也不会被序列化。这其实很好理解,静态字段属于类本身,而不是属于类的某个特定对象实例。序列化保存的是对象的状态,而静态字段是所有对象共享的,它不构成任何一个对象实例的独特状态。所以,无论你序列化多少个对象实例,静态字段的值都不会被保存或恢复。它在反序列化时,依然会保持其在当前JVM中的静态值。

最后,如果一个类的父类没有实现

Serializable
接口,那么该父类的非
transient
、非
static
字段也不会被序列化。当子类被序列化时,只有子类自己的可序列化字段会被保存。反序列化时,父类的字段会调用父类的无参构造器来初始化。这也是为什么如果你的父类没有无参构造器,而子类又实现了
Serializable
,反序列化时可能会抛出
InvalidClassException
,因为它找不到合适的构造器来初始化父类部分。

理解这些“例外”非常重要,它能帮助我们更好地设计可序列化的类,避免数据泄露或不必要的复杂性。

为什么需要给可序列化类定义serialVersionUID?

噢,

serialVersionUID
,这玩意儿初看起来有点神秘,但它在Java序列化机制中扮演着一个至关重要的角色,尤其是在你对类结构进行修改之后。简单来说,它就像是类的一个“版本号”或者“指纹”,用来在序列化和反序列化时进行兼容性检查。

Faceswap
Faceswap

免费开源的AI换脸工具

下载

当你序列化一个对象时,JVM会把这个对象的

serialVersionUID
也写入到序列化流中。当你在另一个JVM或者同一个JVM的不同时间点尝试反序列化这个对象时,JVM会读取流中的
serialVersionUID
,然后拿它与当前类定义中的
serialVersionUID
进行比较。

如果这两个ID不匹配,JVM就会认为“哎呀,这个对象当初序列化的时候,它的类定义和现在的不一样了!”然后,它会毫不留情地抛出

java.io.InvalidClassException
异常。这意味着你无法成功反序列化那个对象。

那么,什么时候这个ID会不匹配呢?当你对一个已经实现了

Serializable
接口的类进行修改时,比如添加、删除、修改字段,或者改变了字段的类型,甚至改变了类名、接口实现等,如果没有显式定义
serialVersionUID
,JVM会根据类的结构自动生成一个。一旦类结构发生变化,自动生成的
serialVersionUID
就会不同。这就是问题所在:你修改了类,即使是很小的改动,之前的序列化数据可能就无法被新的类版本反序列化了。

所以,显式定义

private static final long serialVersionUID = 1L;
(或者任何你觉得合适的版本号)就变得非常重要。一旦你手动定义了它,即使你后续对类进行了添加字段、删除字段等兼容性修改,只要你不改变这个
serialVersionUID
的值,JVM在反序列化时就不会因为版本号不匹配而报错。当然,这只是解决了版本号匹配的问题,数据兼容性(比如新加的字段在老数据中是默认值,老数据中的字段在新类中被删除了怎么办)还需要你自己去处理,比如通过
readObject()
writeObject()
方法进行自定义序列化逻辑。

我个人习惯是,只要一个类实现了

Serializable
,我就会立即给它加上
serialVersionUID
,通常从
1L
开始。这就像是给你的代码买了一份保险,在未来类结构变化时,能避免很多不必要的兼容性问题。

Java序列化在实际开发中有哪些应用场景?

Java序列化在日常开发中其实无处不在,尽管很多时候我们可能没有直接去调用

ObjectOutputStream
ObjectInputStream
,但它常常作为底层机制被各种框架和库所利用。

最直观的一个应用场景就是对象的持久化。如果你想把一个Java对象的状态保存到磁盘上,以便程序关闭后下次启动还能恢复,序列化就是最直接的方式。比如,一个简单的配置对象,或者一个游戏中的存档,都可以通过序列化直接写入文件。虽然现在更流行用JSON、XML或数据库来存储数据,但对于一些简单、内部使用且Java特有的对象,序列化依然很方便。

接着,网络传输是序列化的一个大舞台。在分布式系统中,服务之间经常需要传递对象。Java的RMI(远程方法调用)就是基于序列化来实现的,当客户端调用远程服务器上的一个方法时,参数对象会被序列化后传输到服务器,结果对象也会被序列化后传回客户端。除了RMI,很多消息队列(如Kafka、RabbitMQ)在传输Java对象时,也可能在底层使用序列化机制,或者允许你自定义序列化器来处理Java对象。

缓存也是序列化经常出现的地方。当我们需要将对象存储到内存以外的缓存系统(如Redis、Memcached)中时,对象必须被转换成字节流。虽然这些缓存系统通常支持多种序列化格式(如JSON、Protobuf),但Java原生的序列化也是一种选择,尤其是在纯Java环境下的内部缓存。

再比如,跨进程通信。在同一个机器上,不同的Java进程之间如果需要传递复杂的Java对象,序列化也是一种有效的方式,比如通过管道或者共享内存。

甚至在某些情况下,深拷贝一个对象,你也可以通过先将对象序列化到内存字节数组,再反序列化回来实现。这虽然不是序列化的主要目的,但确实是一种实现深拷贝的“旁门左道”,因为它会创建一个全新的对象图,而不是简单地复制引用。

当然,现在随着微服务和异构系统变得越来越普遍,JSON、Protocol Buffers等跨语言的序列化协议变得更加流行,因为它们解决了Java序列化固有的跨语言兼容性问题。但即便如此,在纯Java的生态系统内部,或者特定场景下,Java原生的序列化依然有着不可替代的地位。它简单、直接,对于那些不需要跨语言交互的内部数据结构来说,是相当高效的。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
rabbitmq和kafka有什么区别
rabbitmq和kafka有什么区别

rabbitmq和kafka的区别:1、语言与平台;2、消息传递模型;3、可靠性;4、性能与吞吐量;5、集群与负载均衡;6、消费模型;7、用途与场景;8、社区与生态系统;9、监控与管理;10、其他特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

202

2024.02.23

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

8

2026.01.28

什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

328

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

235

2023.10.07

json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

418

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

535

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

142

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号