面試官:Transient關(guān)鍵字修飾的變量當(dāng)真不可序列化?
一、寫在開頭
在這篇文章中記錄一下之前自己面試時(shí)學(xué)到的東西,是關(guān)于transient關(guān)鍵字的,當(dāng)時(shí)面試官問我IO的相關(guān)問題,基本上全答出來了,關(guān)于如何不序列化對象中某個字段時(shí),我果斷的選擇了static和transient,但面試官緊接著問了我:“transient關(guān)鍵字修飾的變量當(dāng)真不可序列化嗎?”,這個問題直接給我整不確定了,因?yàn)橐援?dāng)時(shí)的知識儲備,這個問題確實(shí)不知道,最終虛心的向這位面試官請教,他告訴了我答案。
雖然那場面試我還是通過了,但是我沒去,哈哈!不過還是挺感謝那個耐心的面試官的,隨口的一個問題,其實(shí)大部分面試官是不會負(fù)責(zé)給你解答的。
二、案例測試
今天,我們就花一點(diǎn)時(shí)間,來把這個問題梳理一遍。我們先寫一個測試類,去看一下static和transient關(guān)鍵字修飾的字段,在序列化過程中的表現(xiàn):
public class Test {
public static void main(String[] args) throws IOException {
//初始化對象信息
Person person = new Person();
person.setName("JavaBuild");
person.setAge(30);
System.out.println(person.getName()+" "+person.getAge());
//序列化過程
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\person.txt"));) {
objectOutputStream.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
person.par1 = "序列化后靜態(tài)字段";
//反序列化過程
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E:\\person.txt"));) {
Person p = (Person) objectInputStream.readObject();
System.out.println(p);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Person implements Serializable{
private static final long serialVersionUID = 8711922740433840551L;
private String name;
private int age;
public static String par1 = "靜態(tài)字段";
transient String par2 = "臨時(shí)字段";
transient int high = 175;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", par1=" + par1 +
", high=" + high +
", par2='" + par2 + '\'' +
'}';
}
}
輸出:
JavaBuild 30
Person{name='JavaBuild', age=30, par1=序列化后靜態(tài)字段, high=0, par2='null'}
通過打印結(jié)果我們可以看到,static修飾的字段,并沒有參與序列化,讀取到了后面修改的值;transient關(guān)鍵字修飾的字段也沒參與,而且在反序列化過程中,會被重置為默認(rèn)值,例如基本數(shù)據(jù)類型為 0,引用類型為 null。至于原因我們在這里不展開了,上一篇文章里已經(jīng)提到,大家可以去看看。
三、直入主題
我們再回過頭來看看起初的問題:transient 修飾的字段真的不能被序列化?至少通過Serializable接口標(biāo)示的序列化方式里,transient字段時(shí)不可被序列化的,因?yàn)樵谛蛄谢^程中調(diào)用的ObjectStreamClass對象,里面有個方法為getDefaultSerialFields(),已經(jīng)明確的標(biāo)記出了transient修飾符不可被序列化!
圖片
那我們怎么辦呢?
Externalizable接口:
其實(shí)呀,除了 Serializable 之外,Java 還提供了一個序列化接口 Externalizable,它是Serializable的子接口,使用 Externalizable 進(jìn)行反序列化的時(shí)候,會調(diào)用被序列化類的無參構(gòu)造方法去創(chuàng)建一個新的對象,然后再將被保存對象的字段值復(fù)制過去;實(shí)現(xiàn)Externalizable接口時(shí),必須重寫其中的writeExternal() 和 readExternal()方法,我們通過這兩個方法進(jìn)行序列化的設(shè)計(jì)與讀取。
圖片
適應(yīng)場景: 因?yàn)镋xternalizable接口擁有著更高的序列化控制能力,所以在序列化過程中,我們需要對一些敏感信息進(jìn)行加密處理時(shí),它的作用就會體現(xiàn)啦。
我們使用這個接口進(jìn)行序列化嘗試,并且使用transient關(guān)鍵字修飾字段,看一下結(jié)果:
public class Test implements Externalizable {
private transient String text = "我可以被序列化?。?!";
public static void main(String[] args) throws Exception {
Test test = new Test();
//序列化
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\transient.txt"));
out.writeObject(test);
//反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\transient.txt"));
test = (Test)in.readObject();
System.out.println(test.text);
//關(guān)閉流
out.close();
in.close();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(text);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
text = (String) in.readObject();
}
}
輸出:
我可以被序列化?。?!
數(shù)據(jù)成功被序列化到txt文件中,并成功的反序列化讀取到程序中了!即便text被transient修飾著!
四、總結(jié)
通過上面的學(xué)習(xí),我們知道了在Java的序列化中有 Serializable、Externalizable這兩個接口,前者沒有任何方法,只是一個標(biāo)識,而后者作為子類,提供了必須重寫的方法,用以自定義序列化設(shè)計(jì)。此外,transient 關(guān)鍵字只能修飾字段,而不能修飾方法和類,需要注意。