1.從Serilizable說(shuō)到transient
我們知道堵幽,如果一個(gè)對(duì)象需要序列化朴下,那么需要實(shí)現(xiàn)Serilizable
接口殴胧,那么這個(gè)類(lèi)的所有非靜態(tài)屬性佩迟,都會(huì)被序列化报强。
注意:上面說(shuō)的是非靜態(tài)屬性秉溉,因?yàn)殪o態(tài)屬性是屬于類(lèi)的坚嗜,而不是屬于類(lèi)對(duì)象的诗充,而序列化是針對(duì)類(lèi)對(duì)象的操作蝴蜓,所以這個(gè)根本不會(huì)序列化。下面我們可以實(shí)驗(yàn)一下:
實(shí)體類(lèi)Teacher.class
:
import java.io.Serializable;
class Teacher implements Serializable {
public int age;
public static String SchoolName;
public Teacher(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"age=" + age +
'}';
}
}
測(cè)試代碼SerialTest.java
,基本思路就是初始化的時(shí)候押袍,靜態(tài)屬性SchoolName
為"東方小學(xué)",序列化對(duì)象之后谊惭,將靜態(tài)屬性修改侮东,然后,反序列化,發(fā)現(xiàn)其實(shí)靜態(tài)變量還是修改之后的宽闲,說(shuō)明靜態(tài)變量并沒(méi)有被序列化娩梨。
import java.io.*;
public class SerialTest {
public static void main(String[] args) {
Teacher.SchoolName = "東方小學(xué)";
serial();
Teacher.SchoolName = "西方小學(xué)";
deserial();
System.out.println(Teacher.SchoolName);
}
// 序列化
private static void serial(){
try {
Teacher teacher = new Teacher(9);
FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(teacher);
objectOutputStream.flush();
} catch (Exception exception) {
exception.printStackTrace();
}
}
// 反序列化
private static void deserial() {
try {
FileInputStream fis = new FileInputStream("Teacher.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Teacher teacher = (Teacher) ois.readObject();
ois.close();
System.out.println(teacher.toString());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出的結(jié)果吱殉,證明靜態(tài)變量沒(méi)有被序列化8迨=忍佟涕俗!
Teacher{age=9}
西方小學(xué)
2.序列化屬性對(duì)象的類(lèi)需要實(shí)現(xiàn)Serilizable
接口再姑?
突然想到一個(gè)問(wèn)題元镀,如果有些屬性是對(duì)象,而不是基本類(lèi)型讨永,需不需要改屬性的類(lèi)型也實(shí)現(xiàn)Serilizable
呢卿闹?
問(wèn)題的答案是:需要1仍8苎病氢拥!
下面是實(shí)驗(yàn)過(guò)程:
首先嫩海,有一個(gè)Teacher.java
,實(shí)現(xiàn)了Serializable
,里面有一個(gè)屬性是School
類(lèi)型:
import java.io.Serializable;
class Teacher implements Serializable {
public int age;
public School school;
public Teacher(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"age=" + age +
'}';
}
}
School
類(lèi)型,不實(shí)現(xiàn)Serializable
:
public class School {
public String name;
public School(String name) {
this.name = name;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
'}';
}
}
測(cè)試代碼叁怪,我們只測(cè)試序列化:
import java.io.*;
public class SerialTest {
public static void main(String[] args) {
serial();
}
private static void serial(){
try {
Teacher teacher = new Teacher(9);
teacher.school = new School("東方小學(xué)");
FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(teacher);
objectOutputStream.flush();
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了涣觉,報(bào)錯(cuò)的原因是:School不能被序列化官册,也就是沒(méi)有實(shí)現(xiàn)序列化接口难捌,所以如果我們想序列化一個(gè)對(duì)象根吁,那么這個(gè)對(duì)象的屬性也必須是可序列化的击敌,或者它是transient
修飾的愚争。
java.io.NotSerializableException: com.aphysia.transienttest.School
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.aphysia.transienttest.SerialTest.serial(SerialTest.java:18)
at com.aphysia.transienttest.SerialTest.main(SerialTest.java:9)
當(dāng)我們將School
實(shí)現(xiàn)序列化接口的時(shí)候轰枝,發(fā)現(xiàn)一切就正常了...問(wèn)題完美解決
3.不想被序列化的字段怎么辦?
但是如果有一個(gè)變量不是靜態(tài)變量步淹,但是我們也不想序列化它缭裆,因?yàn)樗赡苁且恍┟艽a等敏感的字段澈驼,或者它是不那么重要的字段筛武,我們不希望增加報(bào)文大小,所以想在序列化報(bào)文中排除該字段内边∧洌或者改字段存的是引用地址竿音,不是真正重要的數(shù)據(jù)春瞬,比如ArrayList
里面的elementData
。
這個(gè)時(shí)候就需要使用transient
關(guān)鍵字颠印,將改字段屏蔽线罕。
當(dāng)我們用transient
修飾School
的時(shí)候:
import java.io.Serializable;
class Teacher implements Serializable {
public int age;
public transient School school;
public Teacher(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"age=" + age +
", school=" + school +
'}';
}
}
import java.io.Serializable;
public class School implements Serializable {
public String name;
public School(String name) {
this.name = name;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
'}';
}
}
執(zhí)行下面序列化和反序列化的代碼:
import java.io.*;
public class SerialTest {
public static void main(String[] args) {
serial();
deserial();
}
private static void serial(){
try {
Teacher teacher = new Teacher(9);
teacher.school = new School("東方小學(xué)");
FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(teacher);
objectOutputStream.flush();
} catch (Exception exception) {
exception.printStackTrace();
}
}
private static void deserial() {
try {
FileInputStream fis = new FileInputStream("Teacher.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Teacher teacher = (Teacher) ois.readObject();
ois.close();
System.out.println(teacher.toString());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果如下袄琳,可以看到teacher
字段反序列化出來(lái)刻蟹,其實(shí)是null,這也是transient
起作用了。
但是注意召调,transient
只能修飾變量蛮浑,但是不能修飾類(lèi)和方法,
4.ArrayList
里面的elementData
都被transient
關(guān)鍵字修飾了,為什么ArrayList
還可以序列化呢剩拢?
這里提一下,既然transient
修飾了ArrayList
的數(shù)據(jù)節(jié)點(diǎn)办素,那么為什么序列化的時(shí)候我們還是可以看到ArrayList
的數(shù)據(jù)節(jié)點(diǎn)呢?
這是因?yàn)樾蛄谢臅r(shí)候:
如果僅僅實(shí)現(xiàn)了
Serializable
接口,那么序列化的時(shí)候商源,肯定是調(diào)用java.io.ObjectOutputStream.defaultWriteObject()
方法,將對(duì)象序列化充甚。然后如果是transient
修飾了該屬性盈蛮,肯定該屬性就不能序列化。
但是,如果我們雖然實(shí)現(xiàn)了Serializable
接口,也transient
修飾了該屬性,該屬性確實(shí)不會(huì)在默認(rèn)的java.io.ObjectOutputStream.defaultWriteObject()
方法里面被序列化了我磁,但是我們可以重寫(xiě)一個(gè)writeObject()
方法,這樣一來(lái),序列化的時(shí)候調(diào)用的就是writeObject()
存谎,而不是java.io.ObjectOutputStream.defaultWriteObject()
。
下面的源碼是ObjectInputStream.writeObject(Object obj)
,里面底層其實(shí)會(huì)有反射的方式調(diào)用到重寫(xiě)的對(duì)象的writeObject()
方法,這里不做展開(kāi)诫钓。
public final void writeObject(Object obj) throws IOException {
// 如果可以被重寫(xiě),那么就會(huì)調(diào)用重寫(xiě)的方法
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
ArrayList
重寫(xiě)的writeOject()
方法如下:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
// 默認(rèn)的序列化對(duì)象的方法
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
// 序列化對(duì)象的值
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
我們可以看到绪杏,writeOject()
里面其實(shí)在里面調(diào)用了默認(rèn)的方法defaultWriteObject()
,defaultWriteObject()
底層其實(shí)是調(diào)用改了writeObject0()
履因。ArrayList
重寫(xiě)的writeOject()
的思路主要是先序列化默認(rèn)的,然后序列化數(shù)組大小,再序列化數(shù)組elementData
里面真實(shí)的元素端逼。這就達(dá)到了序列化元素真實(shí)內(nèi)容的目的顶滩。
5.除了transient,有沒(méi)有其他的方式仅醇,可以屏蔽反序列化节预?
且慢,問(wèn)出這個(gè)問(wèn)題,答案肯定是有的9亍!王浴!那就是Externalizable接口氓辣。
具體情況:Externalizable
意思就是几蜻,類(lèi)里面有很多很多屬性,但是我只想要一部分絮吵,要屏蔽大部分,那么我不想在大部分的屬性前面加關(guān)鍵字transient
,我只想標(biāo)識(shí)一下自己序列化的字段澄暮,這個(gè)時(shí)候就需要使用Externalizable
接口。
show me the code!
首先定義一個(gè)Person.java
,里面有三個(gè)屬性
- age:年齡
- name:名字(被
transient
修飾) - score:分?jǐn)?shù)
實(shí)現(xiàn)了Externalizable
接口自娩,就必須實(shí)現(xiàn)writeExternal()
和readExternal()
方法碎乃。
- writeExternal:將需要序列化的屬性進(jìn)行自定義序列化
- readExternal:將需要反序列化的屬性進(jìn)行自定義反序列化
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class Person implements Externalizable {
public int age;
public transient String name;
public int score;
// 必須實(shí)現(xiàn)無(wú)參構(gòu)造器
public Person() {
}
public Person(int age, String name, int score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
/*
* 指定序列化時(shí)候?qū)懭氲膶傩浴_@里不寫(xiě)入score
*/
out.writeObject(age);
out.writeObject(name);
out.writeObject(score);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
/*
* 指定序列化時(shí)候?qū)懭氲膶傩浴_@里仍然不寫(xiě)入年齡
*/
this.age = (int)in.readObject();
this.name = (String)in.readObject();
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", score='" + score + '\'' +
'}';
}
}
上面的代碼,我們可以看出苛白,序列化的時(shí)候,將三個(gè)屬性都寫(xiě)進(jìn)去了,但是反序列化的時(shí)候禾锤,我們僅僅還原了兩個(gè),那么我們來(lái)看看測(cè)試的代碼:
import java.io.*;
public class ExternalizableTest {
public static void main(String[] args) {
serial();
deserial();
}
private static void serial(){
try {
Person person = new Person(9,"Sam",98);
FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
objectOutputStream.flush();
} catch (Exception exception) {
exception.printStackTrace();
}
}
private static void deserial() {
try {
FileInputStream fis = new FileInputStream("person.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Person person = (Person) ois.readObject();
ois.close();
System.out.println(person);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
測(cè)試結(jié)果如下,就可以發(fā)現(xiàn)其實(shí)前面兩個(gè)都反序列化成功了,后面那個(gè)是因?yàn)槲覀冎貙?xiě)的時(shí)候逼争,沒(méi)有自定義該屬性的反序列化移层,所以沒(méi)有是正常的啦...
Person{age=9, name='Sam', score='0'}
如果細(xì)心點(diǎn),可以發(fā)現(xiàn),有一個(gè)字段是transient
修飾的,不是說(shuō)修飾了尼变,就不會(huì)被序列化么哀澈,怎么序列化出來(lái)了膨报。
沒(méi)錯(cuò),只要實(shí)現(xiàn)了Externalizable
接口够吩,其實(shí)就不會(huì)被transient
左右了强法,只會(huì)按照我們自定義的字段進(jìn)行序列化和反序列化,這里的transient
是無(wú)效的...
關(guān)于序列化的transient
暫時(shí)到這,keep going~
【作者簡(jiǎn)介】:
秦懷,公眾號(hào)【秦懷雜貨店】作者,技術(shù)之路不在一時(shí),山高水長(zhǎng)识埋,縱使緩慢零渐,馳而不息诵盼。這個(gè)世界希望一切都很快风宁,更快戒财,但是我希望自己能走好每一步,寫(xiě)好每一篇文章狼纬,期待和你們一起交流。
此文章僅代表自己(本菜鳥(niǎo))學(xué)習(xí)積累記錄冈欢,或者學(xué)習(xí)筆記盈简,如有侵權(quán)柠贤,請(qǐng)聯(lián)系作者核實(shí)刪除臼勉。人無(wú)完人,文章也一樣瓢谢,文筆稚嫩,在下不才,勿噴,如果有錯(cuò)誤之處,還望指出础倍,感激不盡~