一、什么是 transient
简而言之,被 transient
标记的变量在序列化时会被忽略,当然没有序列化就没有反序列化
看看下面的例子来加深理解:
@Data
class Employee implements Serializable
{
private String firstName;
private String lastName;
private transient String password;
}
//序列化对象
try
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
Employee emp = new Employee();
emp.setFirstName("john");
emp.setLastName("jack");
emp.setPassword("password");
oos.writeObject(emp);
oos.close();
} catch (Exception e)
{
System.out.println(e);
}
// 反序列化
try
{
ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("test.txt"));
Employee readEmpInfo = (Employee) ooi.readObject();
System.out.println(readEmpInfo.getFirstName());
System.out.println(readEmpInfo.getLastName());
System.out.println(readEmpInfo.getPassword());
// john
// jack
// null
ooi.close();
} catch (Exception e)
{
System.out.println(e);
}
可以看到被 transient
修饰的属性反序列化为 null。
二、transient
应用场景
- 对象的某个属性的值,需要根据其他属性来计算的时候,应该把这个属性加上
transient
。
这种属性应该是每次都用程序计算,而不是用序列化来持久化。比如说基于时间戳的值,像年龄,或者需要保存一段时间,这段时间是某个时间戳和当前时间戳的时间段。这种情况就不应该将这种属性值序列化。
- 涉及隐私以及安全问题的属性不应该序列化。
- 本身就没有实现序列化接口的属性,如果被序列化会抛出
NotSerializableException
异常。 - 逻辑道理上不需要序列化。比如说类里面有个
Logger
对象,但是Logger
只是用来写日志,不需要保留这个对象的状态。再比如线程对象,线程对象保留是某个时间点的线程状态,没有持久化的意义。
三、transient
和 final
组合
一个属性同时被 transient
和 final
修饰,如果这个属性属于基本数据类型和 String,那么序列化的时候 transient 会被会忽略掉。因为会被 JVM
认为是常量。
其他引用类型(包括基本数据类型的包装类),即使被 final 修饰,transient
也会生效
四、实际应用举例--HashMap
// HashMap中使用 transient标记的字段
transient Entry table[];
transient int size;
transient int modCount;
transient int hashSeed;
private transient Set entrySet;
可以看出 HashMap
的 key-value 相关的存储都是用 transient 修饰的,也就是说序列化的时候不会被序列化。
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
HashMap
反序列化后,会重新 hash 计算把 key-value 存进去。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于