咱们都知道,新建一个对象的时候实现 Serializeable
接口,但为何要这么作?何时这样子作?这样子作会不会出现幺蛾子?阿粉一个三连差点把本身都问懵逼了……html

那接下来,你们就和阿粉一块儿简单了解一下这个知识点吧……java
序
序列化的定义是:将一个对象编码成一个字节流(I/O);而与之相反的操做被称为反序列化。json
序列化的目的是为了方便数据的传递以及存储到磁盘上(把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时咱们就须要将对象转换成字节流才能进行网络传输。对于这种通用的操做,就出现了序列化来统一这些格式)。数组
简单来讲序列化就是一种用来处理对象流的机制。将对象转化成字节序列后能够保存在磁盘上,或经过网络传输,以达到之后恢复成原来的对象。序列化机制使得对象能够脱离程序的运行而独立存在。微信
使用场景:全部可在网络上传输的对象都必须是可序列化的,好比RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,不然会出错;全部须要保存到磁盘的java对象都必须是可序列化的。好比 Redis 将对象当作字符串存储的时候,若是对象实现了序列化,则只须要将对象直接存储便可(java会自动将对象转换成序列化后的字节流);不然须要本身将对象转换成 json 字符串存储,不过 json 字符串相对更加节省内存空间一些。网络
一般建议:程序建立的每一个JavaBean类都实现 Serializeable
接口。可是实现 Serializeable
接口也须要当心谨慎。正如《Effective Java》中第 74 条提到的那样:app
如何实现序列化
在 Java 中,只要一个类实现了 java.io.Serializable
接口,它就能够被序列化(枚举类也能够被序列化)。eclipse
例如:每一个枚举类型都会默认继承类java.lang.Enum,而Enum类实现了Serializable接口,因此枚举类型对象都是默承认以被序列化的。jvm
// DeletedEnum 类,表示删除状态@Getter@AllArgsConstructorpublic enum DeletedEnum { NO_DELETED(0,"未删除"), DELETED(1,"已删除");
public final Integer status; public final String name;}
下图是 java.lang.Enum
类:编辑器

而一个普通的类想实现序列化,只须要实现 Serializable 接口便可:
@Datapublic class User implements Serializable { //序列化版本号 private static final long serialVersionUID = 1111013L;
transient private String name; private int age;
public static void main(String[] args) { User user = new User(); user.setAge(12); user.setName("小路飞"); System.out.println(user); }}
输出结果以下:
User(name=小路飞, age=12)
那为何一个类实现了 Serializable 接口,它就能够被序列化呢?
这是由于它使用 ObjectOutputStream 来持久化对象到文件中,使用了 writeObject 方法,该方法又调用了以下方法:
/** * Underlying writeObject/writeUnshared implementation. */ private void writeObject0(Object obj, boolean unshared) throws IOException { …… // remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } …… }
从上述代码可知,若是被写对象的类型是String,或数组,或 Enum,或 Serializable,那么就能够对该对象进行序列化,不然将抛出 NotSerializableException。
即:String 类型的对象、枚举类型的对象、数组对象,都是默承认以被序列化的,并生成默认的序列化版本号。
我看网上的资料都有讲使用 Externalizable 接口和使用 transient 关键字来实现序列化的方法,但阿粉感受用的很少,因此在这里就不过多的赘述了,何况它们其实都是基于 Serializable 接口实现的序列化。
如何自动生成序列化版本号
idea IDE
安装 serialVersionUID 插件便可。

eclipse
通常来讲有两种生成方式:
-
一个是默认的1L,好比: private static final long serialVersionUID = 1L;
-
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。实现序列化后,类名上会出现黄色波浪下划线,选择第一项,添加已生成的串行版本标识便可自动生成一个。
序列化版本号的用处
在 java.io.Serializable
的文档中的解释是这样的:
大体意思是以下:
由于反序列化必须拥有 class 文件,但随着项目的升级,class 文件也会升级,序列化怎么保证升级先后的兼容性呢?
序列化运行时与每一个可序列化的类关联一个版本号,称为 serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。若是接收方已为该对象加载了一个与相应发送方类具备不一样的 serialVersionUID 的类,则反序列化将致使 InvalidClassException。可序列化的类能够经过声明一个名为 serialVersionUID 的字段来显式声明其本身的 serialVersionUID,该字段必须是静态的,最终的且类型为 long:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
只要版本号相同,即便更改了序列化属性,对象也能够正确被反序列化回来。
@Datapublic class User implements Serializable { //序列化版本号 private static final long serialVersionUID = 1111013L; private String name; private int age;}
若是反序列化使用的 class 的版本号与序列化时使用的不一致,反序列化会报 InvalidClassException 异常。
若是可序列化的类未明确声明 serialVersionUID ,则序列化运行时将根据该类的各个方面为该类计算默认的 serialVersionUID 值,如Java(TM)对象序列化规范中所述。可是,强烈建议全部可序列化的类显式声明 serialVersionUID 值,由于默认的 serialVersionUID 计算对类详细信息高度敏感,而类详细信息可能会根据编译器的实现而有所不一样,所以可能在反序列化期间致使意外的 InvalidClassExceptions。
并且,默认值不利于 jvm 间的移植,可能class文件没有更改,但不一样 jvm 可能计算的规则不同,这样也会致使没法反序列化。
所以,为了保证不一样Java编译器实现之间的 serialVersionUID 值一致,可序列化的类必须声明一个显式的 serialVersionUID 值。强烈建议显式 serialVersionUID 声明在可能的状况下使用 private 修饰符,由于此类声明仅适用于当即声明的类 serialVersionUID字段做为继承成员不起做用。
最后,使用默认机制在序列化对象时,不只会序列化当前对象,还会对该对象引用的其它对象也进行序列化,一样地,这些其它对象引用的另外对象也将被序列化,以此类推。因此,若是一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
参考
-
https://www.cnblogs.com/kubixuesheng/p/10350523.html -
https://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it -
https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf -
《Effective Java》中文版第二版
< END >
若是你们喜欢咱们的文章,欢迎你们转发,点击在看让更多的人看到。也欢迎你们热爱技术和学习的朋友加入的咱们的知识星球当中,咱们共同成长,进步。
本文分享自微信公众号 - Java极客技术(Javageektech)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。