在查看了一些Java Collection类的源代码之后,我发现使用transient
总是修改成员变量。
例如,LinkedList
源代码:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
public LinkedList()
{
header.next = header.previous = header;
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
// ...other stuff
}
当然,不仅LinkedList
使用瞬态,几乎每个Java集合类都使用transient
来修改其成员变量的至少一半。
所以,我的问题是:为什么transient
在Java标准库中使用如此广泛?
(当然每个人都知道transient
的定义和用法,但这不是我的问题:)
答案 0 :(得分:7)
从序列化的角度来看 序列化整个对象时,不会序列化瞬态变量。
如果您不希望某些变量被序列化,那么您可以将其变为瞬态
从你的例子中,LinkedList是序列化的。
如果仔细观察,所有变为瞬态的变量都会以编程方式进行维护。所以没有必要坚持下去。
例如size
,当您回读任何序列化对象时,您只需阅读Node<E>
并以编程方式维护大小。因此无需序列化size
。请记住LinkedList
的实际数据不是size
。如果您拥有entries
的实际数据,您可以随时计算其大小,并且这样更容易。
如需参考,请查看。
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
答案 1 :(得分:1)
当你编写一个非平凡的可序列化类(比POJO复杂得多的东西)时,通常最好将类代表的内容与实际实现分开。
执行此操作的一种方法是通过transient
字段并提供readObject()/writeObject()
方法来控制以序列化形式写入的值以及如何在反序列化时初始化transient
字段。
更强大的解决方案是序列化代理,它们实际上使用readResolve()/writeReplace()
来序列化完全不同的对象。 (可以在EnumSet
中找到此模式的示例。)
这些技术的主要优点是它们允许您更改实现,而无需更改类的序列化形式。这是一件好事,因为如果您正在编写API,API对象的序列化形式是公共API的一部分,它代表了一个承诺,即使用以前版本序列化的对象将在以后的版本中反序列化。 (或者你的API最终会像Swing一样,所有类Javadoc都会在其中携带an ugly warning。)
它们还提供一些保护,防止恶意制作的序列化对象破坏您的类不变量。 (想象一下,如果链接列表按原样序列化,并且有人修改了结果,以便条目指向自身作为其后继,导致迭代在无限循环中运行。)