首页 > Java > java教程 > 正文

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

雪夜
发布: 2025-08-19 19:53:01
原创
807人浏览过

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序列化机制中扮演着一个至关重要的角色,尤其是在你对类结构进行修改之后。简单来说,它就像是类的一个“版本号”或者“指纹”,用来在序列化和反序列化时进行兼容性检查。

序列猴子开放平台
序列猴子开放平台

具有长序列、多模态、单模型、大数据等特点的超大规模语言模型

序列猴子开放平台 0
查看详情 序列猴子开放平台

当你序列化一个对象时,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原生的序列化依然有着不可替代的地位。它简单、直接,对于那些不需要跨语言交互的内部数据结构来说,是相当高效的。

以上就是java怎样实现对象的序列化与反序列化 java序列化操作的基础方法的详细内容,更多请关注php中文网其它相关文章!

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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