一、什么是 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 存进去。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于