在阅读很多开源项目中在很多类中都会发现存在一个使用static final 修饰的量,通常很大而且和代码似乎没有什么规律。
比如下面的代码:

public class Dml implements Serializable {private static final long erialVersionUID = 2611556444074013268L;

(代码来自阿里巴巴开源项目Client Adapter)

概述

所述的serialVersionUID属性是用来序列的标识符/反序列化的对象序列化类。

序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。

如果接收者为对象加载的类serialVersionUID与相应的发送者的类不同,则反序列化将导致 InvalidClassException。可序列化的类可以serialVersionUID通过声明一个serialVersionUID必须为static,final和type的字段来显式声明其自身long:

private static final long serialVersionUID = 42L;

如果可序列化的类未显式声明一个 serialVersionUID,则序列化运行时将根据serialVersionUID该类的各个方面为该类计算默认值,如Java对象序列化规范中所述。

但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认serialVersionUID计算对类详细信息高度敏感,而类详细信息可能会根据编译器的实现而有所不同,因此可能在InvalidClassExceptions反序列化期间导致意外情况。

因此,为了保证serialVersionUID不同Java编译器实现之间的值一致,可序列化的类必须声明一个显式serialVersionUID值。还强烈建议明确serialVersionUID声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类-serialVersionUID字段作为继承成员没有用。

序列号UID

简而言之,我们使用serialVersionUID属性记住Serializable类的版本,以验证加载的类和序列化的对象是否兼容。

不同类的serialVersionUID属性是独立的。因此,不同的类不必具有唯一的值。

接下来,让我们通过一些示例来学习如何使用 serialVersionUID。

首先创建一个可序列化的类,并声明一个serialVersionUID标识符:

//代码来自baeldung
public class AppleProduct implements Serializable {private static final long serialVersionUID = 1234567L;public String headphonePort;public String thunderboltPort;
}

接下来,我们将需要两个实用程序类:一个用于将AppleProduct对象序列化为String,另一个用于从该String反序列化该对象:

//代码来自baeldung
public class SerializationUtility {public static void main(String[] args) {AppleProduct macBook = new AppleProduct();macBook.headphonePort = "headphonePort2020";macBook.thunderboltPort = "thunderboltPort2020";String serializedObj = serializeObjectToString(macBook);System.out.println("Serialized AppleProduct object to string:");System.out.println(serializedObj);}public static String serializeObjectToString(Serializable o) {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(o);oos.close();return Base64.getEncoder().encodeToString(baos.toByteArray());}
}
//代码来自baeldung
public class DeserializationUtility {public static void main(String[] args) {String serializedObj = ... // ommited for claritySystem.out.println("Deserializing AppleProduct...");AppleProduct deserializedObj = (AppleProduct) deSerializeObjectFromString(serializedObj);System.out.println("Headphone port of AppleProduct:"+ deserializedObj.getHeadphonePort());System.out.println("Thunderbolt port of AppleProduct:"+ deserializedObj.getThunderboltPort());}public static Object deSerializeObjectFromString(String s)throws IOException, ClassNotFoundException {byte[] data = Base64.getDecoder().decode(s);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));Object o = ois.readObject();ois.close();return o;}
}

我们从运行SerializationUtility.java开始,该程序将AppleProduct对象保存(序列化)为String实例,并使用Base64对字节进行编码。

然后,使用该String作为反序列化方法的参数,我们运行DeserializationUtility.java,该程序从给定的String重新组装(反序列化)AppleProduct对象。

生成的输出应与此类似:

Serialized AppleProduct object to string:
rO0ABXNyACljb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkFwcGxlUHJvZHVjdAAAAAAAEta
HAgADTAANaGVhZHBob25lUG9ydHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADmxpZ2h0ZW5pbmdQb3
J0cQB+AAFMAA90aHVuZGVyYm9sdFBvcnRxAH4AAXhwdAARaGVhZHBob25lUG9ydDIwMjBwdAATd
Gh1bmRlcmJvbHRQb3J0MjAyMA==
Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020

现在,让我们 在AppleProduct.java中修改serialVersionUID常量,然后重新尝试从先前产生的同一String反序列化AppleProduct对象。重新运行DeserializationUtility.java应该生成此输出。

//代码来自baeldung
Deserializing AppleProduct...
Exception in thread "main" java.io.InvalidClassException: com.baeldung.deserialization.AppleProduct; local class incompatible: stream classdesc serialVersionUID = 1234567, local class serialVersionUID = 7654321at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)at com.baeldung.deserialization.DeserializationUtility.deSerializeObjectFromString(DeserializationUtility.java:24)at com.baeldung.deserialization.DeserializationUtility.main(DeserializationUtility.java:15)

通过更改类的serialVersionUID,我们修改了其版本/状态。结果,在反序列化期间未找到兼容的类,并且引发了InvalidClassException。

如果Serializable类中未提供serialVersionUID,则JVM将自动生成一个。但是,优良作法是提供serialVersionUID值,并在更改类后对其进行更新,以便我们可以控制序列化/反序列化过程。我们将在下一部分中对其进行仔细研究。

兼容的变更

假设我们需要在现有的AppleProduct类中添加一个新的lightningPort字段:

public class AppleProduct implements Serializable {//...public String lightningPort;
}

因为我们只是增加一个新的领域,在没有变化的serialVersionUID将需要。这是因为,在反序列化过程中,会将null分配为lightningPort字段的默认值。

让我们修改DeserializationUtility类以打印此新字段的值:

System.out.println("LightningPort port of AppleProduct:"+ deserializedObj.getLightningPort());

现在,当我们重新运行DeserializationUtility类时,我们将看到类似以下的输出:

Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020
Lightning port of AppleProduct:null

默认串行版本

如果我们不为Serializable 类定义 serialVersionUID 状态 ,则Java将根据类本身的某些属性(例如,类名,实例字段等)定义一个。

让我们定义一个简单的 Serializable 类:

public class DefaultSerial implements Serializable {}

如果我们像下面这样序列化此类的实例:

DefaultSerial instance = new DefaultSerial();
System.out.println(SerializationUtility.serializeObjectToString(instance));

这将打印序列化二进制文件的Base64摘要:

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

和以前一样,我们应该能够从摘要中反序列化此实例:

String digest = "rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpY" + "WxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw";
DefaultSerial instance = (DefaultSerial) DeserializationUtility.deSerializeObjectFromString(digest);

但是,对该类进行一些更改可能会破坏序列化兼容性。例如,如果我们向此类添加一个 私有 字段:

public class DefaultSerial implements Serializable {private String name;
}

然后尝试将相同的Base64摘要反序列化为类实例,我们将收到InvalidClassException:

Exception in thread "main" java.io.InvalidClassException: com.baeldung.deserialization.DefaultSerial; local class incompatible: stream classdesc serialVersionUID = 9045863543269746292, local class serialVersionUID = -2692722436255640434

由于这种不必要的不​​兼容性,在Serializable类中声明serialVersionUID 始终是一个好主意。 这样,我们可以随着类本身的发展来保留或发展版本。

结论

在这篇快速文章中,我们演示了如何使用serialVersionUID常量来简化序列化数据的版本控制。

部分内容翻译自Java Doc 和 baeldung

什么是serialVersionUID?serialVersionUID详解相关推荐

  1. private static final long serialVersionUID = 1L;详解

    public class User implements Serializable {/*** serialVersionUID*/private static final long serialVe ...

  2. private static final long serialVersionUID = 1L详解

    public class User implements Serializable {/*** serialVersionUID*/private static final long serialVe ...

  3. es springboot 不设置id_es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程详解

    注意:适用于springboot或者springcloud框架 1.首先下载相关文件 2.然后需要去启动相关的启动文件 3.导入相关jar包(如果有相关的依赖包不需要导入)以及配置配置文件,并且写一个 ...

  4. 十二、springboot 详解RestControllerAdvice(ControllerAdvice)

    springboot 详解RestControllerAdvice(ControllerAdvice)拦截异常并统一处理 简介 @Target({ElementType.TYPE}) @Retenti ...

  5. Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例

    摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! "看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然.所以,无论什么事情,仔 ...

  6. java objectoutputstream怎么用_java序列化与ObjectOutputStream和ObjectInputStream的实例详解...

    java序列化与ObjectOutputStream和ObjectInputStream的实例详解 一个测试的实体类: public class Param implements Serializab ...

  7. java 值对象_java 中设计模式(值对象)的实例详解

    java 中设计模式(值对象)的实例详解 应用场景:在Java开发时,需要来回交换大量的数据,比如要为方法传入参数,也要获取方法的返回值,该如何能更好的进行数据的交互?这个时候就需要用到我们的值对象设 ...

  8. Java 注解用法详解——@SuppressWarnings

    转自: https://www.cnblogs.com/fsjohnhuang/p/4040785.html Java魔法堂:注解用法详解--@SuppressWarnings 一.前言 编码时我们总 ...

  9. LSTM入门必读:从入门基础到工作方式详解 By 机器之心2017年7月24日 12:57 长短期记忆(LSTM)是一种非常重要的神经网络技术,其在语音识别和自然语言处理等许多领域都得到了广泛的应用

    LSTM入门必读:从入门基础到工作方式详解 By 机器之心2017年7月24日 12:57 长短期记忆(LSTM)是一种非常重要的神经网络技术,其在语音识别和自然语言处理等许多领域都得到了广泛的应用. ...

  10. Spring事务管理(详解+实例)

    写这篇博客之前我首先读了<Spring in action>,之后在网上看了一些关于Spring事务管理的文章,感觉都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章如下: ...

最新文章

  1. source insight c++ namespace 无法跳转解决方法
  2. 浅淡绿萝2.0和星火计划
  3. SAP Kyma和Marketing Cloud的连接 - Marketing Cloud里的配置
  4. ABAP maintenance view event handling
  5. cobbler koan自动重装系统
  6. 4 操作系统第二章 进程管理 进程控制、通信
  7. 谷歌搜索技巧:搜索语法+隐藏彩蛋+高级设置
  8. NSMutableArray遍历删除注意事项
  9. 济南学习 Day 5 T2 am
  10. 系统集成资质-信息系统项目管理师考试综合介绍
  11. Github使用教程Git下载文件
  12. 常见的四种硬盘接口介绍
  13. 如何做好会员营销 三步教你看懂会员管理
  14. PHP输出星座,php 通过日期推算星座的方法
  15. 使用torchvision 中的roi_pool/roi_align函数时报错
  16. 自动驾驶的分级和无人驾驶系统简介
  17. 284、超详细的光纤熔纤、盘纤教程,值得收藏
  18. ASEMI整流模块MSAD165-16参数,MSAD165-16规格
  19. 当面试官问:JS中原始类型有哪些?
  20. 如何解决Java查看源代码时页面显示Source not found

热门文章

  1. 正确的座机号码格式_电话号码的正确写法
  2. 篆刻学简体——第一章
  3. RPLIDAR A1 slam建图
  4. API接口文档编写--易文档
  5. matlab谢尔宾斯三角_几何画板教程:谢尔宾斯基三角形的制作
  6. Redis缓存雪崩解决方案
  7. editormd编辑器在flask中的使用
  8. TBase集群安装配置
  9. 主析取范式与主合取范式
  10. window新建文本快捷键