Java中transient字段的作用
技术背景
在Java编程中,序列化是一个重要的概念。序列化是指将对象的状态转换为字节流的过程,以便将其持久化存储(如保存到文件)或在网络中传输。而反序列化则是将字节流恢复为对象状态的过程。在某些情况下,我们可能不希望对象的某些字段参与序列化过程,这时就需要用到transient
关键字。
实现步骤
理解序列化
要使用transient
关键字,首先要理解序列化的概念。在Java中,要使一个类的对象能够被序列化,该类必须实现Serializable
接口,这是一个标记接口,没有任何方法。示例如下:
1 2 3 4 5
| import java.io.Serializable;
class MyClass implements Serializable { }
|
使用transient关键字
当一个类实现了Serializable
接口后,默认情况下,类的所有非静态成员变量都会被序列化。如果希望某个成员变量不被序列化,可以将其声明为transient
。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.io.Serializable;
class NameStore implements Serializable { private String firstName; private transient String middleName; private String lastName;
public NameStore(String fName, String mName, String lName) { this.firstName = fName; this.middleName = mName; this.lastName = lName; }
public String toString() { StringBuffer sb = new StringBuffer(40); sb.append("First Name : "); sb.append(this.firstName); sb.append("Middle Name : "); sb.append(this.middleName); sb.append("Last Name : "); sb.append(this.lastName); return sb.toString(); } }
|
序列化和反序列化对象
以下是一个完整的示例,展示了如何进行对象的序列化和反序列化:
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
| import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;
class NameStore implements Serializable { private String firstName; private transient String middleName; private String lastName;
public NameStore(String fName, String mName, String lName) { this.firstName = fName; this.middleName = mName; this.lastName = lName; }
public String toString() { StringBuffer sb = new StringBuffer(40); sb.append("First Name : "); sb.append(this.firstName); sb.append("Middle Name : "); sb.append(this.middleName); sb.append("Last Name : "); sb.append(this.lastName); return sb.toString(); } }
public class TransientExample { public static void main(String args[]) throws Exception { NameStore nameStore = new NameStore("Steve", "Middle", "Jobs"); ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("nameStore")); o.writeObject(nameStore); o.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("nameStore")); NameStore nameStore1 = (NameStore) in.readObject(); System.out.println(nameStore1); } }
|
在上述示例中,middleName
被声明为transient
,因此在反序列化后,middleName
的值为null
。
核心代码
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
| import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;
class GalleryImage implements Serializable { private Image image; private transient Image thumbnailImage;
private void generateThumbnail() { }
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); generateThumbnail(); } }
public class Main { public static void main(String[] args) throws Exception { GalleryImage galleryImage = new GalleryImage();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("galleryImage.ser")); oos.writeObject(galleryImage); oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("galleryImage.ser")); GalleryImage deserializedImage = (GalleryImage) ois.readObject(); ois.close(); } }
|
在这个示例中,thumbnailImage
被声明为transient
,在序列化时不会被保存。在反序列化时,通过重写readObject
方法重新生成缩略图。
最佳实践
标记可推导的字段
如果一个字段的值可以从其他字段推导出来,那么可以将其标记为transient
。例如,一个表示利息的字段interest
,其值可以通过本金、利率和时间计算得出,就不需要将其序列化。
避免序列化敏感信息
对于包含敏感信息的字段,如密码、信用卡号等,应该将其标记为transient
,以防止这些信息在序列化过程中被泄露。
处理不可序列化的对象
如果类中包含不可序列化的对象,如Thread
、Socket
等,应该将这些对象的引用标记为transient
,否则在序列化时会抛出NotSerializableException
异常。
常见问题
为什么不使用注解@DoNotSerialize
这是因为在Java引入注解之前,transient
关键字就已经存在了。在当时,使用关键字是一种简单有效的解决方案。
静态字段是否需要标记为transient
静态字段不会被序列化,因为静态字段属于类,而不是对象。所以即使不标记为transient
,静态字段也不会参与序列化过程。但如果为了代码的可读性和明确性,也可以将其标记为transient
。
transient字段在反序列化后的值是什么
transient
字段在反序列化后的值为其默认值,如引用类型为null
,基本类型为相应的默认值(如int
为0,boolean
为false
等)。如果需要在反序列化后对transient
字段进行初始化,可以重写readObject
方法。