为什么自定义序列化?

这里直接举一个书上的例子

public final class StringList implements Serializable {private int size = 0;private Entry head = null;private static class Entry implements Serializable {String data;Entry next;Entry previous;}....
}

我们知道,序列一个类,会同时序列化它的组件
也就是说,如果我序列化了“B”对象, B是双向链表,它要序列化它的内部成员“A”和“C”对象,但是序列化“A”和“C”对象的时候,B同时也是它们的组件,也要序列化“B”~~~~~~~~
于是就进入了无穷的死循环中!!!

这时候,我们的需求很简单,对于每个对象的Entry,我只序列化一次就行了,不需要迭代序列化。
于是就有了transient关键字

public final class StringList implements Serializable {private static final long serialVersionUID = 1L;private transient int size = 0; //不会被序列化private transient Entry head = null;private static class Entry {String data;Entry next;Entry previous;}public final void add(String s) { ... }/*** Serialize this {@code StringList} instance* * @serialData The size of the list (the number of strings it contains)* is emitted ({@code int}), followed by all of its elements (each a * {@code String}), in the proper sequence.*/private void writeObject(ObjectOutputStream s) throws IOException {s.defaultWriteObject();s.writeInt(size);for(Entry e = head; e != null; e = e.next ) {s.writeObject(e.data);}}private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {s.defaultReadObject();int num = s.readInt();for(int i=0; i < num; i++) {add((String)s.readObject());}}.....
}

标记为transient的不会自动序列化,这就防止默认序列化做出错误的事情,然后调用writeObject手动序列化transient字段,做自己认为正确的事。readObject同理。

注意⚠️:尽管StringList的所有域都是transient,但writeObject和readObject的首要任务仍是调用defaultXxxObject方法来序列化不带有transient的字段,这样可以极大的增强灵活性。因为万一以后加入了不带有transient的字段呢?

总结,自定义序列化目的就是做自己认为正确的事情,经典的例子有ArrayList和HashMap。

自定义序列化的例子

ArrayList

来看一下关键的实例域:

  private static final Object[] EMPTY_ELEMENTDATA = {};/*** Shared empty array instance used for default sized empty instances. We* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when* first element is added.*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.*/transient Object[] elementData; // non-private to simplify nested class access/*** The size of the ArrayList (the number of elements it contains).** @serial*/private int size;

这里定义空数组是为什么呢?
答案就在这里:Effective Java之返回零长度的数组或者集合,而不是null(四十三)

然后为什么transient Object[] elementData;
难道不应该初始化它的所有数组元素吗?
原因就是ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际elementData里面有可能有1个实际对象,其他都是null,默认的初始化会初始化99个null对象,这显然不合适!
所以,我们来欣赏一下它的writeObject方法:

    private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();// Write out size as capacity for behavioural compatibility with clone()s.writeInt(size);// Write out all elements in the proper order.for (int i=0; i<size; i++) {s.writeObject(elementData[i]);}//prevent concurrent modificationif (modCount != expectedModCount) {throw new ConcurrentModificationException();}}

HashMap

   transient Node<K,V>[] table;

原因1

因为读写Map是根据Object.hashcode()来确定从table[i]读/写,而Object.hashcode()是native方法, 不同的JVM里可能是不一样的。比如向HashMap存一个键值对entry, key为字符串"hello", 在第一个java程序里, "hello"的hashcode()为1, 存入table【1】在另一个JVM程序里, "hello" 的hashcode()有可能就是2, 存入table【2】所以不管物理结构,序列化只负责把key,value送货上门,具体放在哪个table,序列化不需要理会。

原因2:

table 和 ArrayList的elementData  中存储的值数量是小于数组的大小的(数组扩容的原因),这个在元素越来越多的情况下更为明显。如果使用默认的序列化,那些没有元素的位置也会被存储,就会产生很多不必要的浪费。

Effective Java之考虑自定义的序列化模式(七十五)相关推荐

  1. Gavin老师Transformer直播课感悟 - Rasa对话机器人项目实战之教育领域Education Bot项目Form解析及自定义全解(七十五)

    本文继续围绕工业级业务对话平台和框架Rasa,对Rasa对话机器人项目实战之教育领域Education Bot项目关于Form的定义,如何激活一个form或者使当前运行的form进入不激活状态,如何在 ...

  2. 【零基础学Java】—字符串的概述和特点(十五)

    [零基础学Java]-字符串的概述和特点(十五) 一.字符串的特点 java.lang.String类代表字符串 API当中说:Java程序中的所有字符串字面值(如:"abc")都 ...

  3. Effective Java读书笔记八:序列化(74-78)

    第74条:谨慎地实现Serializable接口 对象序列化API,它提供了一个框架,用来将对象编码成字节流,并从字节流编码中重新构建对象.将一个对象编码成一个字节流,称作将该对象序列化,相反的处理过 ...

  4. effective java 3th item2:考虑 builder 模式,当构造器参数过多的时候

    yiaz 读书笔记,翻译于 effective java 3th 英文版,可能有些地方有错误.欢迎指正. 静态工厂方法和构造器都有一个限制:当有许多参数的时候,它们不能很好的扩展. 比如试想下如下场景 ...

  5. Effective Java之注解优于命名模式(三十五)

    Java 1.5之前,一般使用命名模式表明有些程序元素需要通过某种工具或者框架进行特殊处理.例如,JUnit测试框架原本要求用户一定要用test作为测试方法名称的开头. 命名模式的缺点: 文字拼写错误 ...

  6. 《Effective Java》读书笔记 - 11.序列化

    Chapter 11 Serialization Item 74: Implement Serializable judiciously 让一个类的实例可以被序列化不仅仅是在类的声明中加上" ...

  7. Effective Java之考虑用序列化代理代理序列化实例(七十八)

    我们知道,实现了序列化的类.在反序列化时,实例的创建是由readObject方法来完成的.由于这是一个不同于构造函数的创建类实例的通道,因此在构造函数中的状态约束条件在readObjetc中也得一条不 ...

  8. Effective Java之用enum代替int常量(三十)

    1.Int枚举常量 public class mytest {public static final int num_one = 1;public static final int num_two = ...

  9. Effective Java之优先使用标准的异常(六十)

    Java平台类库提供了一组基本的未受检的异常,他们满足了绝大部分API的异常抛出异常. 为什么优先使用标准异常 1.它使你的API可读性更强,因为它与程序员习惯的用法一致. 2.异常类越少,程序在类装 ...

最新文章

  1. Linux-重启与压缩命令
  2. python虚拟环境和pyenv_Python多版本管理器pyenv和虚拟环境pyenv-virtualenv的安装设置...
  3. 鸿蒙系统被泼冷水,给鸿蒙泼冷水:见不得同行的好,是人间最可恶的蠢和恶
  4. Java使用InetAddress类获取主机名和IP地址
  5. linux 转码软件,分享|Linux 桌面中 4 个开源媒体转换工具
  6. Linux 中 的 vi 编辑模式 直接对文件进行修改~
  7. go build不从本地gopath获取_Go语言实战打包和工具链
  8. Winform开发的快速、健壮、解耦的几点建议
  9. 把chrome的多微博插件FaWave变成桌面程序!
  10. Java *1.11(人口估算)美国人口调查局基于以下假设进行人口估算:
  11. 长沙动环监控系统主要监测哪些指标呢?
  12. cli命令行配置路由器_Cisco路由CLI基础命令
  13. 米家?华为?阿里?Homekit?有没有你在用的智能家居平台?
  14. 【2018.12.14】python3.7 一个低级趣味的爬虫(requests+pyquery)妹纸的图哇咔咔
  15. 【软件工程】敏捷宣言
  16. 运动场球具的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  17. 简单的c#winform画图工具
  18. 数据采集简单示例:采集爱帮网电话号码
  19. 快抖“变长”、爱优腾“变短”
  20. mysql advisor github_GitHub - zyw/sqladvisor-web: 美团SQLAdvisor SQL优化建议工具的Web版,告别命令行...

热门文章

  1. linux常见问题及其解决方案集锦
  2. Netty学习笔记(二)Netty服务端流程启动分析
  3. Java集合框架:总结
  4. 搞定系统设计 02:估算的一些方法
  5. 力扣- -去除重复字母
  6. STL中算法锦集(二)
  7. 章琦:能坚持的唯一的原因就是兴趣
  8. Kubernetes Ingress 控制器的技术选型技巧
  9. 卡牌特效: svg不规则倒计时动效
  10. TSRC白帽子,10亿用户的守护者