<h2 class="rich_media_title" id="activity-name">

        讲真,下次打死我也不敢随便改serialVersionUID了
</h2>
<div id="meta_content" class="rich_media_meta_list"> <span class="rich_media_meta rich_media_meta_nickname" id="profileBt">
<a id="js_name">
码农沉思录 </a>
<div id="js_profile_qrcode" class="profile_container" style="display:none;">
<div class="profile_inner">
<strong class="profile_nickname">码农沉思录</strong>
<img class="profile_avatar" id="js_profile_qrcode_img" src="" alt=""> <p class="profile_meta">
<label class="profile_meta_label">微信号</label>
<span class="profile_meta_value">code-thinker</span>
</p> <p class="profile_meta">
<label class="profile_meta_label">功能介绍</label>
<span class="profile_meta_value">笔者为国内某知名企业不知名码农,专注Java Web领域多年,有丰富的bug开发经验。</span>
</p> </div>
<span class="profile_arrow_wrp" id="js_profile_arrow_wrp">
<i class="profile_arrow arrow_out"></i>
<i class="profile_arrow arrow_in"></i>
</span>
</div>
</span> <em id="publish_time" class="rich_media_meta rich_media_meta_text">3天前</em> </div> <div class="rich_media_content " id="js_content"> <p style="font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: right;" data-mpa-powered-by="yiban.io"><span style="color: rgba(0, 0, 0, 0.65);font-family: 'Monospaced Number', 'Chinese Quote', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;text-align: start;white-space: pre;letter-spacing: 1.4px;font-size: 12px;"></span></p><p style="text-align: center;"><img class="rich_pages " data-ratio="0.666" data-s="300,640" data-src="https://mmbiz.qpic.cn/mmbiz_jpg/XA3sPCPib1l5P34dAsQQPqibib8tyCUCMnwX6RXYICMHYibO8b56dpM8cuHhew6LceId2PJiaEbdsKDZ4Eggzur5UyA/640?wx_fmt=jpeg" data-type="jpeg" data-w="500" style="width: 500px !important; height: auto !important; visibility: visible !important;" _width="500px" src="" crossorigin="anonymous" data-fail="0"></p><section class="code-snippet__fix code-snippet__js"><pre class="code-snippet__js" data-lang="javascript"><code class="hljs"><span class="code-snippet_outer">来源:Java成长路</span></code><code class="hljs less"><span class="code-snippet_outer">链接:<span class="hljs-attribute"><span class="hljs-attribute">https</span></span>:<span class="code-snippet__comment"><span class="hljs-comment"><span class="hljs-comment">//www.jianshu.com/p/a4508a8f2420</span></span></span></span></code></pre></section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。类通过实现 java.io.Serializable 接口以启用其序列化功能。</section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">但是,还有一个知识点并未展开介绍,那就是关于serialVersionUID 。这个字段到底有什么用?如果不设置会怎么样?为什么《阿里巴巴Java开发手册》中有以下规定:</section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;"><img class="" data-ratio="0.1527777777777778" data-type="other" data-w="1080" data-src="https://mmbiz.qpic.cn/mmbiz/CvQa8Yf8vq11tWicgbQ0jTiaGle6Dibibo4ZXozI7y0sxrnbthHA5eZ8qRHia8kegttJeOTzWuEAttacty5mBibQHvuw/640?wx_fmt=other" _width="677px" src="https://img2018.cnblogs.com/blog/1112483/201909/1112483-20190909143819446-485477025.png" crossorigin="anonymous" data-fail="0"></section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;"><strong style="color: rgb(191, 54, 12);">背景知识</strong></section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">在展开本文的介绍之前,先来简单介绍一些和序列化有关的知识,</section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;"><strong style="color: rgb(191, 54, 12);">Serializable 和 Externalizable</strong></section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">Java类通过实现 java.io.Serializable 接口以启用其序列化功能。<strong style="color: rgb(191, 54, 12);">未实现此接口的类将无法进行序列化或反序列化。</strong>可序列化类的所有子类型本身都是可序列化的。</section><p style="color: rgb(62, 62, 62);font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 16px;white-space: normal;widows: 1;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);text-align: center;"></p><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">如果读者看过Serializable的源码,就会发现,他只是一个空的接口,里面什么东西都没有。<strong style="color: rgb(191, 54, 12);">Serializable接口没有方法或字段,仅用于标识可序列化的语义。</strong>但是,如果一个类没有实现这个接口,想要被序列化的话,就会抛出java.io.NotSerializableException异常。</section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?</section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;">原因是在执行序列化的过程中,会执行到以下代码:</section><section style="font-size: 14px;letter-spacing: 1.4px;white-space: normal;line-height: 2em;margin: 1.5em 5px !important;"><img class="" data-ratio="0.5575485799701047" data-type="other" data-w="669" data-src="https://mmbiz.qpic.cn/mmbiz/CvQa8Yf8vq11tWicgbQ0jTiaGle6Dibibo4Zr7iaP8yEwHzfzaicHTBfibGaF3BZbCKiboDanJqzLzuKLxUTeBbcPI8klA/640?wx_fmt=other" _width="669px" src="https://img2018.cnblogs.com/blog/1112483/201909/1112483-20190909145454097-584363508.png

" crossorigin="anonymous" data-fail="0">

在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果都不是则直接抛出NotSerializableException。
Java中还提供了Externalizable接口,也可以实现它来提供序列化能力。
Externalizable继承自Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。
当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。否则所有变量的值都会变成默认值。
transient
transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
自定义序列化策略
在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。
如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject方法以及 ObjectInputStream 的 defaultReadObject 方法。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
所以,对于一些特殊字段需要定义序列化的策略的时候,可以考虑使用transient修饰,并自己重写writeObject 和 readObject 方法,如java.util.ArrayList中就有这样的实现。
以上,就是一些读者需要掌握和和序列化有关的知识。
我们随便找几个Java中实现了序列化接口的类,如String、Integer等,我们可以发现一个细节,那就是这些类除了实现了Serializable外,还定义了一个serialVersionUID
那么,到底什么是serialVersionUID呢?为什么要设置这样一个字段呢?
什么是serialVersionUID
序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。
而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化ID,就是我们在代码中定义的serialVersionUID。
如果serialVersionUID变了会怎样
我们举个例子吧,看看如果serialVersionUID被修改了会发生什么?
我们先执行以上代码,把一个User1对象写入到文件中。然后我们修改一下User1类,把serialVersionUID的值改为2L。
然后执行以下代码,把文件中的对象反序列化出来:
执行结果如下:
java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
可以发现,以上代码抛出了一个java.io.InvalidClassException,并且指出serialVersionUID不一致。
这是因为,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
这也是《阿里巴巴Java开发手册》中规定,在兼容性升级中,在修改类的时候,不要修改serialVersionUID的原因。除非是完全不兼容的两个版本。所以,serialVersionUID其实是验证版本一致性的。
如果读者感兴趣,可以把各个版本的JDK代码都拿出来看一下,那些向下兼容的类的serialVersionUID是没有变化过的。比如String类的serialVersionUID一直都是-6849794470754667710L。
但是,作者认为,这个规范其实还可以再严格一些,那就是规定:
如果一个类实现了Serializable接口,就必须手动添加一个private static final long serialVersionUID变量,并且设置初始值。
为什么要明确定一个serialVersionUID
如果我们没有在类中明确的定义一个serialVersionUID的话,看看会发生什么。
尝试修改上面的demo代码,先使用以下类定义一个对象,该类中不定义serialVersionUID,将其写入文件。
然后我们修改User1类,向其中增加一个属性。在尝试将其从文件中读取出来,并进行反序列化。
执行结果:
java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402
同样,抛出了InvalidClassException,并且指出两个serialVersionUID不同,分别是-2986778152837257883和7961728318907695402。
从这里可以看出,系统自己添加了一个serialVersionUID。
所以,一旦类实现了Serializable,就建议明确的定义一个serialVersionUID。不然在修改类的时候,就会发生异常。
serialVersionUID有两种显示的生成方式:
一种是默认的1L,比如:
private static final long serialVersionUID = 1L;
另外一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;
后面这种方式,可以借助IDE生成,后面会介绍。
背后原理
知其然,要知其所以然,我们再来看看源码,分析一下为什么serialVersionUID改变的时候会抛异常?在没有明确定义的情况下,默认的serialVersionUID是怎么来的?
为了简化代码量,反序列化的调用链如下:
在initNonProxy中 ,关键代码如下:
在反序列化过程中,对serialVersionUID做了比较,如果发现不相等,则直接抛出异常。
深入看一下getSerialVersionUID方法:
在没有定义serialVersionUID的时候,会调用computeDefaultSUID 方法,生成一个默认的serialVersionUID。
这也就找到了以上两个问题的根源,其实是代码中做了严格的校验,并且在未定义的时候自动生成了一个serialVersionUID。
IDEA提示
为了确保我们不会忘记定义serialVersionUID,可以调节一下Intellij IDEA的配置,在实现Serializable接口后,如果没定义serialVersionUID的话,IDEA(eclipse一样)会进行提示:
并且可以一键生成一个:
当然,这个配置并不是默认生效的,需要手动到IDEA中设置一下:
在图中标号3的地方(Serializable class without serialVersionUID的配置),打上勾,保存即可。
总结
serialVersionUID是用来验证版本一致性的。所以在做兼容性升级的时候,不要改变类中serialVersionUID的值。
特别说明一下,由于本文标题并不完全能表达本文的全部内容,这里再强调一下:serialVersionUID 既然是验证版本一致性的,在做版本升级的时候(非兼容性升级),记得要修改这个字段的值哦,这样可以避免序列化混乱。
如果一个类实现了Serializable接口,一定要记得定义serialVersionUID,否则会发生异常。可以在IDE中通过设置,让他帮忙提示,并且可以一键快速生成一个serialVersionUID。
之所以会发生异常,是因为反序列化过程中做了校验,并且如果没有明确定义的话,会根据类名及属性等自动生成一个。

            <div class="read-more__area" id="js_more_read_area" style="display:none;">

            </div>

                            <div class="reward_area tc reward_area_primary" id="js_preview_reward_author" style="display:none;">

                        </div>

原文地址:https://mp.weixin.qq.com/s/HP4P4IcD8oTEovBUSq54pA

讲真,下次打死我也不敢随便改serialVersionUID了的更多相关文章

  1. 讲真,MySQL索引优化看这篇文章就够了

    本文主要讨论MySQL索引的部分知识.将会从MySQL索引基础.索引优化实战和数据库索引背后的数据结构三部分相关内容,下面一一展开. 一.MySQL——索引基础 首先,我们将从索引基础开始介绍一下什么 ...

  2. 讲真,这两个IDE插件,可以让你写出质量杠杠的代码

    昨晚躺在床上看<拯救大兵瑞恩>的时候,不由得感叹道:"斯皮尔伯格的电影质量真高,片头真实地还原了二战的残酷性."看完后,我的精神异常的亢奋,就想写篇文章来帮助大家提高一 ...

  3. 讲真,你是因为什么才买华为P20系列手机!

    华为P20系列手机上市两个半月发货600万台!600万台?!看到这个亮瞎我钛合金狗眼的数据,且容我掰着手指脚趾算一下,算了,还是容我毫不夸张的感叹一句吧:华为做手机不用桨,不需风,全靠“浪”……. 两 ...

  4. 讲真,这两款idea插件,能治愈你英语不好的病

    时不时就有小伙伴问我,"二哥,能推荐一款 IDE 吗?"你看这话问的,现在搞 Java 的不都在用 Intellij IDEA 吗,还用得着推荐(我已经和 Eclipse 分手了) ...

  5. ARCH-LINUX 折(安)腾(装)记

    2016-08-09 前几天装的manjaro一直卡住,今天想换archlinux.... 先联个网.....wifi-menu 写入U盘 tuna 下载了ISO UltraISO 写进U盘,结果出问 ...

  6. PE文件格式详解,第一讲,DOS头文件格式

    PE文件格式详解,第一讲,DOS头文件格式 今天讲解PE文件格式的DOS头文件格式 首先我们要理解,什么是文件格式,我们常说的EXE可执行程序,就是一个文件格式,那么我们要了解它里面到底存了什么内容 ...

  7. React Native (0.57)开发环境搭建(注意:Node不要随便更新到最新版,更新完后莫名其妙的问题一大堆)

    搭建开发环境 一.安装依赖 必须安装的依赖有:Node.Watchman 和 React Native 命令行工具以及 Xcode. 1.首先安装 Homebrew 2.安装 Node, Watchm ...

  8. Mac 上真机调试cocos2d-x-3.16的test程序

    文章比较长,一个算是新手又不是新手的程序员的解决过程. 一 xcode中打开项目 首先,下载完成cocos2d-x-3.16之后,解压,然后在根目录build目录下双击cocos2d_tests.xc ...

  9. 清北学堂2018DP&图论精讲班 DP部分学习笔记

    Day 1 上午 讲的挺基础的--不过还是有些地方不太明白 例1 给定一个数n,求将n划分成若干个正整数的方案数. 例2 数字三角形 例7 最长不下降子序列 以上太过于基础,不做深入讨论 例3 给定一 ...

随机推荐

  1. Css中的!important

    转载自:https://www.cnblogs.com/cang12138/p/7326280.html !important为开发者提供了一个增加样式权重的方法,比直接在元素的 style 属性中设 ...

  2. js利用递归与promise 按顺序请求数据

    问题:项目中有一个需求,一个tabBar下面如果没有内容就不让该tabBar显示,当然至于有没有内容,需要我们通过请求的来判断,但是由于请求是异步的,如何让请求按照tabBar的顺序进行? 方案:我们 ...

  3. vue,一路走来(17)--底部tabbar切换

    <router-link></router-link>存在router-link-active属性,那么底部tabbar切换就简单多了.不会再出现刷新回到第一个的bug. &l ...

  4. MySQL06-- mysql索引

    目录 一.索引介绍 1.什么是索引 2.索引类型介绍 3.索引管理 5.索引操作 6.前缀索引 7.联合索引 8.创建索引总结: 一.索引介绍 1.什么是索引 1)索引就好比一本书的目录,它能让你更快 ...

  5. spark 计算结果写入mysql 案例及常见问题解决

    package com.jxd import org.apache.spark.SparkContextimport org.apache.spark.SparkConfimport java.sql ...

  6. 厉害了!新AI人工智能研究令人大开眼界

    AI人工智能有很多方法可以操纵照片,让您看起来更好看,消除红眼或镜头眩光等等.但到目前为止,眨眼已经证明了一个顽强的快照对手. 这远远不是智能“绘画中”的唯一例子,因为当一个程序用它认为属于的地方填充 ...

  7. python 关于每个项目的解释器

    在写程序的时候发现了一个问题,就是我原来import的第三方包,在新建的项目里仍然报错,还需要重新下载一次 发现:原来每建一个新项目,python会默认生成另一个新的解释器和相关虚拟环境,包括第三方包 ...

  8. springboot redis操作

    redis五大类型用法 Redis五大类型:字符串(String).哈希/散列/字典(Hash).列表(List).集合(Set).有序集合(sorted set)五种Controller:@Reso ...

  9. C#高级编程笔记 (1至6章节)数组,类/方法/泛型

    2.3变量 var 类型推断 type 类的分类 如:type nametype = name.GetType(); //取变量name的类型 const 常量  const int painame ...

  10. Yii2 kineditor

    用 kineditor实现异步 <table cellspadding=5 width=400> <tr height='150'> <td valign="t ...