Java - 为什么 transient 成员变量在 Java 标准库中使用如此广泛?
Java - why transient member variables used so widely in Java standard library?
看了一些Java集合类的源码,发现成员变量总是被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
使用transient,几乎每个Java集合类都使用transient
修改至少一半的成员变量。
所以,我的问题是:为什么 transient
在 Java 标准库中如此广泛地使用 ?
(transient
的定义和用法当然大家都知道,但这不是我的问题:)
从序列化的角度
序列化整个对象时不会序列化瞬态变量。
当你不希望某些变量被序列化时,你可以将其设为瞬态
根据您的示例,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++;
}
当您编写一个非平凡的可序列化 class(比 POJO 复杂得多的东西)时,通常最好将 class 表示的内容与实际执行。
一种方法是通过 transient
字段并提供 readObject()/writeObject()
方法来控制以序列化形式写入的值以及如何在反序列化时初始化 transient
字段。
一个更强大的解决方案是序列化代理,它们实际上序列化一个完全不同的对象,而不是使用 readResolve()/writeReplace()
。 (此模式的示例可在 EnumSet
中找到。)
这些技术的主要优点是它们允许您更改实现而无需更改 class 的序列化形式。这是一件好事,因为如果你正在编写一个 API,你的 API 对象的序列化形式是 public API 的一部分,它代表了对象的承诺用以前的版本序列化的将在以后的版本中反序列化。 (或者您的 API 最终会像 Swing 一样,其中所有 class Javadoc 都包含 an ugly warning。)
它们还提供一些保护措施,防止恶意制作的序列化对象破坏您的 class 不变量。 (想象一下,如果链表按原样序列化,并且有人修改了结果,使得一个条目指向自己作为它的后继者,导致无限循环中的迭代 运行。)
看了一些Java集合类的源码,发现成员变量总是被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
使用transient,几乎每个Java集合类都使用transient
修改至少一半的成员变量。
所以,我的问题是:为什么 transient
在 Java 标准库中如此广泛地使用 ?
(transient
的定义和用法当然大家都知道,但这不是我的问题:)
从序列化的角度 序列化整个对象时不会序列化瞬态变量。
当你不希望某些变量被序列化时,你可以将其设为瞬态
根据您的示例,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++;
}
当您编写一个非平凡的可序列化 class(比 POJO 复杂得多的东西)时,通常最好将 class 表示的内容与实际执行。
一种方法是通过 transient
字段并提供 readObject()/writeObject()
方法来控制以序列化形式写入的值以及如何在反序列化时初始化 transient
字段。
一个更强大的解决方案是序列化代理,它们实际上序列化一个完全不同的对象,而不是使用 readResolve()/writeReplace()
。 (此模式的示例可在 EnumSet
中找到。)
这些技术的主要优点是它们允许您更改实现而无需更改 class 的序列化形式。这是一件好事,因为如果你正在编写一个 API,你的 API 对象的序列化形式是 public API 的一部分,它代表了对象的承诺用以前的版本序列化的将在以后的版本中反序列化。 (或者您的 API 最终会像 Swing 一样,其中所有 class Javadoc 都包含 an ugly warning。)
它们还提供一些保护措施,防止恶意制作的序列化对象破坏您的 class 不变量。 (想象一下,如果链表按原样序列化,并且有人修改了结果,使得一个条目指向自己作为它的后继者,导致无限循环中的迭代 运行。)