上周在群里有小盆友问 transient 关键字是干什么的。这篇文章就以此为契机介绍一下 transient 的作用,以及在 ArrayList 里面的应用。
要了解 transient 我们先聊聊 Java 的序列化。
复习序列化
所谓序列化是指,把对象转化为字节流的一种机制。同理,反序列化指的就是把字节流转化为对象。

- 对于 Java 对象来说,如果使用 JDK 的序列化实现。对象需要实现
java.io.Serializable 接口。
- 可以使用
ObjectOutputStream() 和 ObjectInputStream() 对对象进行序列化和反序列化。
- 序列化的时候会调用
writeObject() 方法,把对象转换为字节流。
- 反序列化的时候会调用
readObject() 方法,把字节流转换为对象。
- Java 在反序列化的时候会校验字节流中的
serialVersionUID 与对象的 serialVersionUID 时候一致。如果不一致就会抛出 InvalidClassException 异常。官方强烈推荐为序列化的对象指定一个固定的 serialVersionUID。否则虚拟机会根据类的相关信息通过一个摘要算法生成,所以当我们改变类的参数的时候虚拟机生成的 serialVersionUID 是会变化的。
transient 关键字修饰的变量 不会 被序列化为字节流
1、ArrayList 是基于数组实现的,是一个动态数组,容量支持自动自动增长
2、ArrayList 线程不安全
3、ArrayList 实现了 Serializable,支持序列化
勤俭持家
上文我们说到 ArrayList 是基于数组实现,我们看看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
transient Object[] elementData;
private int size;
|
有几个重要的信息:
- ArraryList 是动态数组,这个 elementData 就是存储对象的数据。
- 这个数组居然使用了 transient 来修饰。
- 数组的长度等于 ArrayList 的容量。而不是 ArrayList 的元素数量。
- size 是指的 ArrayList 中元素的数量,不是动态数组的长度。
- size 没有被 transient 修饰,是可以被序列化的。
这,怎么回事。ArrayList 存储数据的数组,居然不需要序列化?

莫慌,我们继续往下看代码。上文我们说过,对象的序列化和反序列化是通过调用方法 writeObject() 和 readObject() 完成了,我们发现,ArrayList 自己实现这两个方法看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ int expectedModCount = modCount; s.defaultWriteObject();
s.writeInt(size);
for (int i=0; i<size; i++) { s.writeObject(elementData[i]); }
if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt();
if (size > 0) { int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); ensureCapacityInternal(size); Object[] a = elementData; for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
|
注意,在 writeObject() 方法中,
1 2 3 4
| for (int i=0; i<size; i++) { s.writeObject(elementData[i]); }
|
按需序列化,用了几个下标序列化几个对象。
读取的时候也是:
1 2 3
| for (int i=0; i<size; i++) { a[i] = s.readObject(); }
|
有几个读几个。
总结一下:
- 被
transient 修饰的变量不会被序列化。
- ArrayList 的底层数组
elementData 被 transient 修饰,不会直接被序列化。
- 为了实现 ArrayList 元素的序列化,ArrayList 重写了
writeObject() 和 readObject() 方法。
- 按需序列化数组,只序列化存在的数据,而不是序列化整个
elementData 数组。
用多少,序列化多少,真是勤俭持家的 ArrayList。
有趣的代码系列
那些有趣的代码(一)–有点萌的 Tomcat 的线程池
那些有趣的代码(二)–偏不听父母话的 Tomcat 类加载器
欢迎关注我的微信公众号
