【Java基础】序列化、反序列化和不可变类

news2025/3/13 10:23:33

Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
🌱🌱个人主页:奋斗的明志
🌱🌱所属专栏:Java基础面经

📚本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。

在这里插入图片描述

Java基础

  • 一、Java中的序列化和反序列化是什么?
    • 1、序列化
      • 1.1 Java序列化关键类和接口
    • 2、反序列化
    • 3、注意点
      • 3.1 transient 关键字
      • 3.2 serialVersionUID
      • 3.3 序列化性能问题
      • 3.4 安全性
    • 4、序列化和反序列化理解
      • 4.1 Java 序列化 Serializable 的意义
      • 4.2 serialVersionUID 又有什么用?
      • 4.2 Java 序列化不包含静态变量
  • 二、什么是 Java 中的不可变类?
    • 1、特征
      • 1.1 不可变类 Person 实现
      • 1.2 验证不可变性
    • 2、不可变类的优缺点
      • 2.1 优点
      • 2.2 缺点
    • 3、举例 String

一、Java中的序列化和反序列化是什么?

应用场景:
网络传输、远程调用、持久化存储(比如:保存数据到文件或者数据库)、以及分布式系统中数据交换。

1、序列化

在Java中 序列化就是把 对象转换为字节流
这样对象可以通过网络传输、持久化存储或者缓存
Java 提供了 java.io.Serializable 接口来支持序列化,只要类实现了这个接口,就可以将该类的对象进行序列化.

1.1 Java序列化关键类和接口

  • 通过实现 Serializable 接口,然后用 ObjectOutputStreamObjectInputStream
  • ObjectOutputStream 用于序列化,ObjectInputStream 用于反序列化
  • 类必须 实现 Serializable 接口 才能被序列化

2、反序列化

是将字节流重新转换为对象的过程,即从存储中读取数据,并重新创建对象

3、注意点

3.1 transient 关键字

  • 在序列化过程中,有些字段不需要被序列化,例如:敏感数据,可以使用 transient 关键字 标记不需要序列化的字段。

3.2 serialVersionUID

  • 每一个 Serializable 类都应该定义一个 serialVersionUID,用于在反序列化时验证版本的一致性。如果没有明确指定 serialVersionUID,Java会根据类的定义自动生成一个 UID ,版本不匹配可能导致反序列化失败。

3.3 序列化性能问题

  • Java默认的序列化机制可能比较慢,尤其是对于大规模分布式系统,可能会选择更加高效的序列化框架
  • 比如:Protobuf、Kryo

3.4 安全性

  • 反序列化是一个潜在的安全风险,因为通过恶意构造的字节流,可能会加载不安全的类 或者 执行不是期望的代码。因此,反序列化过程需要进行输入验证,避免反序列化漏洞。

4、序列化和反序列化理解

序列化其实就是将对象转化为可传输的字节序列格式,以便于存储和传输。

  • 因为对象在 JVM 中可以认为是“立体的”,会有各种引用。
  • 比如在内存地址中 Ox666 引用了某某某对象,呢此时这个对象要传输到网络的另一端的时候,就需要把这些引用“压扁”。
  • 因为网络另一端的内存地址 Ox666 可以没有某某某对象,所以传输的对象需要包含这些信息,然后接收端将这些扁平的信息再反序列化得到对象。
  • 所以,反序列化就是将字节序列格式转换为对象的过程

在这里插入图片描述

4.1 Java 序列化 Serializable 的意义

在这里插入图片描述

Serializable 接口没有什么实际的含义,就是起到一个标记的作用

  • 观看一下源码就会非常清楚,除了String,数组,枚举之外,如果实现了 serializable 这个接口就走 writeOrdinaryObject,否则序列化就会抛出异常。

在这里插入图片描述

4.2 serialVersionUID 又有什么用?

private static final long serialVersionUID = 1L;

想必经常会看到这样的代码,这个 ID 其实就是用来验证序列化的对象和反序列化对应的对象的 ID 是否是一致的。所以这个 ID 的数字其实不重要,无论是 1L 还是 idea 自动生成的,只要序列化的时候对象 serialVersionUID和反序列化的时候对象的 serialVersionUlD 一致的话就行。如果没有显式指定 serialVersionUlD 则编译器会根据类的相关信息自动生成一个,可以认为是一个指纹。所以如果你没有定义一个 serialVersionUID 然后序列化个对象之后,在反序列化之前把对象的类的结构改了,比如增加了一个成员变量,则此时的反序列化会失败。因为类的结构变了,生成的指纹就变了,所以 serialVersionUID 就不一致了.

4.2 Java 序列化不包含静态变量

简单地说就是序列化之后存储的内容不包含静态变量的值,看一下下面的代码就很清晰了。

import java.io.*;

public class Test implements Serializable {
    //指纹
    private static final long serialVersionUID = 1L;

    public static int n = 1;

    public static void main(String[] args) {
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(
                    new FileOutputStream("明志学编程!"));
            outputStream.writeObject(new Test());
            outputStream.close();

            //序列化后修改值
            Test.n = 2;

            ObjectInputStream objectInputStream = new ObjectInputStream(
                    new FileInputStream("明志学编程!"));
            Test t = (Test) objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(t.n);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

二、什么是 Java 中的不可变类?

不可变类 是指在创建后其状态(对象的字段)无法被修改的类。一旦对系被创建,它的所有属性都不能被修改。也就是说,对象的所有成员变量在初始化后就不能被修改,无论是通过外部方法还是内部方法。这种类的实例整个生命周期内保持不变。

1、特征

  • 字段声明为 final,防止子类继承
  • 该类的所有字段都是 private 和 final,确保只能在构造函数中初始化,之后不能修改。另外,不要提供 serter 方法,因为 setter 会改变变量的值。
  • 如果类中有可变对象的引用,比如一个 Date 对象,那么在获取该对象的时候应该返回其副本而不是原对象,防止外部修改影响内部状态。
  • 通过构造函数初始化所有字段
  • 如果有一个可变对象,比如一个Date类型的birthDate,那么在getter中应该返回birthDate的克隆或者新的Date对象,而不是直接返回引用,这样可以避免外部的修改影响到Person类内部的状态。
  • 可能还需要注意深拷贝的问题,特别是在构造函数中传入可变对象时,应该进行拷贝,而不是直接引用。比如,如果构造函数的参数是Date,应该创建一个新的Date对象保存其值,这样外部的Date对象后续变化不会影响Person内部的birthDate。
  • 另外,确保类不会被扩展,所以类本身要声明为final,这样不会有子类覆盖方法导致状态变化的风险。

Java中经典不可变类有 : String,Integer,BigDecimal,LocalDate

1.1 不可变类 Person 实现

public final class Person {
    // 1. 成员变量声明为 private final
    private final String name;
    private final int age;
    private final List<String> hobbies;

    // 2. 通过构造函数初始化所有成员变量
    public Person(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        // 3. 对可变对象进行深拷贝(防御性拷贝)
        this.hobbies = new ArrayList<>(hobbies);
    }

    // 4. 不提供 setter 方法
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // 5. 返回可变对象的不可变视图或深拷贝
    public List<String> getHobbies() {
        return Collections.unmodifiableList(hobbies);
        // 或者返回深拷贝:return new ArrayList<>(hobbies);
    }
}

1.2 验证不可变性

public class Main {
    public static void main(String[] args) {
        List<String> hobbies = new ArrayList<>();
        hobbies.add("Reading");
        Person person = new Person("Alice", 30, hobbies);

        // 尝试修改原始 hobbies
        hobbies.add("Cooking");
        System.out.println("Original Hobbies: " + hobbies); // [Reading, Cooking]

        // 不可变类的 hobbies 未被修改
        System.out.println("Person's Hobbies: " + person.getHobbies()); // [Reading]

        // 尝试通过 getHobbies() 修改(会抛出 UnsupportedOperationException)
        person.getHobbies().add("Gaming");
    }
}

在这里插入图片描述

2、不可变类的优缺点

2.1 优点

  • 线程安全:由于不可变对象的状态不能被修改,他们天生就是线程安全的,在并发环境中无需同步。
  • 缓存友好:不可变对象可以安全的被缓存和共享,如 string 的字符常量池。
  • 防止状态不一致:不可变类可以有效避免因意外修改对象状态而导致的不一致问题。

2.2 缺点

  • 性能问题:不可变对象需要在每次状态变化时创建新的对象,这可能会导致性能开销,尤其是对于大规模对象或频繁修改的场景(例如String频繁拼接)

3、举例 String

String就是典型的不可变类,当你创建一个String对象之后,这个对象就无法被修改。
因为无法被修改,所以像执行S += “a” ,这样的方法,其实返回的是一个新建的String对象,老的 S 指向的对象不会发生变化,只是 S 的引用指向了新的对象而已。
所以不要在字符串拼接频繁的场景使用+来拼接,因为这样会频繁的创建对象。
不可变类的好处就是安全,因为知晓这个对象不可能会被修改,因此可以放心大胆的用,在多线程环境下也是线程安全的。

查看 String 的源码,发现 String 类是 final 修饰的,表示无法被继承。
在这里插入图片描述

String本质是一个char数组,然后用final修饰,不过 final 限制不了数组内部的数据,所以这还不够。所以value是用private修饰的,并且没有暴露出set方法,这样外部其实就接触不到vlue所以无法修改。

当然还是有修改的需求,比如replace方法,所以这时候就需要返回一个新对象来作为结果。

在这里插入图片描述

就是私有化变量,然后不要暴露St方法,即使有修改的需求也是返回一个新对象。

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2295101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USB子系统学习(四)使用libusb读取鼠标数据

文章目录 1、声明2、HID协议2.1、描述符2.2、鼠标数据格式 3、应用程序4、编译应用程序5、测试 1、声明 本文是在学习韦东山《驱动大全》USB子系统时&#xff0c;为梳理知识点和自己回看而记录&#xff0c;全部内容高度复制粘贴。 韦老师的《驱动大全》&#xff1a;商品详情 …

深度剖析 Redisson 分布式锁:原理、实现与应用实践

文章目录 写在文章开头详解Redisson 分布式锁使用和实现前置准备工作分布式锁的基本使用公平锁的使用联锁的使用读写锁基本使用常见问题Redisson和Jedis有什么区别redisson如何实现分布式锁redisson如何实现分布式锁的可重入redisson如何实现公平锁Redisson的watchdog机制是什么…

基于微信小程序的医院预约挂号系统的设计与实现

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…

仅128个token达到ImageNet生成SOTA性能!MAETok:有效的扩散模型的关键是什么?(卡内基梅隆港大等)

论文链接&#xff1a;https://arxiv.org/pdf/2502.03444 项目链接&#xff1a;https://github.com/Hhhhhhao/continuous_tokenizer 亮点直击 理论与实验分析&#xff1a;通过实验和理论分析建立了潜空间结构与扩散模型性能之间的联系。揭示了具有更少高斯混合模型&#xff08;G…

示例:JAVA调用deepseek

近日&#xff0c;国产AI DeepSeek在中国、美国的科技圈受到广泛关注&#xff0c;甚至被认为是大模型行业的最大“黑马”。在外网&#xff0c;DeepSeek被不少人称为“神秘的东方力量”。1月27日&#xff0c;DeepSeek应用登顶苹果美国地区应用商店免费APP下载排行榜&#xff0c;在…

Linux系统命令无法使用(glib库相关问题)

1.背景描述 Yum强制安装了一些软件&#xff0c;安装软件成功无报错&#xff0c;完成后不久突然发现系统出问题了&#xff0c;所有的命令无法使用了&#xff0c;如ls、mv、cat等基本命令报错。 relocation error&#xff1a; /lib64/libpthread.so.0: symbol_libc_dl_error_tsd …

电脑黑屏按什么键恢复?电脑黑屏的解决办法

电脑黑屏的原因有很多&#xff0c;可能是硬件、软件、系统或者病毒等方面造成的。那么&#xff0c;当我们遇到电脑黑屏时&#xff0c;应该怎么做呢&#xff1f;有没有什么快捷的方法可以恢复正常呢&#xff1f;本文将为您介绍一些常见的电脑黑屏情况及其解决办法。 一、电脑开机…

思翼遥控器疑问?

1.地面端与遥控端对频&#xff0c;地面端选择数传2为串口&#xff0c;天空端的UART2通过USB转TTL模块连接电脑&#xff0c;通过串口助手观察得有1Hz输出帧&#xff08;开启遥控器APP时间段为10Hz&#xff09;&#xff0c;共21字节&#xff0c;请问&#xff0c;这个是什么含义&a…

anaconda中可以import cv2,但是notebook中cv2 module not found

一、问题 anaconda中成功import cv2 但是jupyter notebook中却无法导入cv2 二、排查 anaconda中使用python路径如下&#xff1a; jupyter notebook中使用python路径如下&#xff1a; 可以发现路径不一致。 三、解决 ①查看可用的kernel ②选中想要修改的kernel&#xff0c;打…

如何解决 Linux 文件系统挂载失败的问题

当遇到Linux文件系统挂载失败的问题时&#xff0c;您可以通过以下步骤来解决问题&#xff1a; 解决方法&#xff1a; 检查挂载点&#xff1a; 确保要挂载的目标文件系统存在&#xff0c;并且挂载点是正确的。检查挂载点是否已经被其他文件系统占用。 检查文件系统状态&#x…

PHP填表统计预约打卡表单系统小程序

&#x1f4cb; 填表统计预约打卡表单系统——专属定制&#xff0c;信息互动新纪元 &#x1f4ca; 填表统计预约打卡表单系统&#xff0c;一款专为现代快节奏生活量身打造的多元化自定义表单统计小程序&#xff0c;集信息填表、预约报名、签到打卡、活动通知、报名投票、班级统…

PAT乙级( 1009 说反话 1010 一元多项式求导)C语言版本超详细解析

1009 说反话 给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过 80的字符串。字符串由若干单词和若干空格组成&#xff0c;其中单词是由英文字母&#x…

LVSNAT服务搭建

LVSNAT实验环境搭建 在虚拟机上&#xff0c;我的NAT模式ip划分为&#xff1a;172.25.254.0 仅主机模式IP为&#xff1a;192.168.0.0 拓补图如下 配置服务&#xff1a;LVS服务端添加两个网卡&#xff0c;分别为NAT模式和仅主机模式 LVS服务端配置&#xff1a; systemctl st…

apisix网关ip-restriction插件使用说明

ip-restriction插件可以在网关层进行客户端请求ip拦截。 当然了&#xff0c;一般不推荐使用该方法&#xff0c;专业的事专业工具做。建议有条件&#xff0c;还是上防火墙或者waf来做。 官方文档&#xff1a;ip-restriction | Apache APISIX -- Cloud-Native API Gateway whit…

html 列动态布局

样式说明&#xff1a; /* 列动态布局&#xff0c;列之间以空格填充 */ li {display: flex;/* flex-direction: column; */justify-content: space-between; }

C++小等于的所有奇数和=最大奇数除2加1的平方。

缘由 三种思路解题&#xff1a;依据算术推导得到一个规律&#xff1a;小等于的所有奇数和等于最大奇数除以2加1的平方。将在后续发布&#xff0c;总计有十种推导出来的实现代码。 int a 0,aa 1,aaa 0;cin >> a; while (aa<a) aaa aa, aa 2;cout << aaa;i…

政采云业务网关实践:使用 Higress 统一替代 APISIX/Kong/Istio Ingress

作者&#xff1a;政采云基础架构团队技术专家 朱海峰&#xff08;片风&#xff09; 业务网关项目背景 由于一些历史的背景&#xff0c;政采云平台在网关建设上遇到一些问题&#xff1a; 容器网关配置较多&#xff0c;配置方式多样&#xff0c;运维压力较大&#xff1a; 配置…

【嵌入式 Linux 音视频+ AI 实战项目】瑞芯微 Rockchip 系列 RK3588-基于深度学习的人脸门禁+ IPC 智能安防监控系统

前言 本文主要介绍我最近开发的一个个人实战项目&#xff0c;“基于深度学习的人脸门禁 IPC 智能安防监控系统”&#xff0c;全程满帧流畅运行。这个项目我目前全网搜了一圈&#xff0c;还没发现有相关类型的开源项目。这个项目只要稍微改进下&#xff0c;就可以变成市面上目前…

C语言:深入了解指针4(超级详细)

看之前必须得掌握有一定指针的知识&#xff0c;不然会看不懂&#xff0c;如果有不懂的可以看我博客 指针1&#xff0c;指针2&#xff0c;指针3 这三个讲了指针全部的基础知识超级详细&#xff0c;这篇只要是讲一些指针练习题也是非常详细 1. sizeof和strlen的对⽐ 1. 基本定义…

CEF132 编译指南 Windows 篇 - 拉取 CEF 源码 (五)

1. 引言 获取 CEF 132 源码是开始编译工作的前提和关键步骤。在完成 depot_tools 的安装和配置后&#xff0c;我们需要通过正确的方式下载和同步 CEF 的源代码。由于 CEF 项目依赖于 Chromium 的大量组件&#xff0c;因此源码的获取过程需要特别注意同步策略和版本管理&#x…