Java 序列化
序列化作用
注意事項(xiàng)
Java 序列化的缺點(diǎn)
舉例說明
項(xiàng)目中曾遇到的一個(gè)小問題
參考
序列化定義:將一個(gè)對(duì)象編碼成一個(gè)字節(jié)流,稱作將該對(duì)象序列化,反之,將字節(jié)流重新構(gòu)建成對(duì)象,則稱作反序列化。
序列化作用
序列化將對(duì)象編碼成字節(jié)流,主要用于對(duì)象的持久化,遠(yuǎn)程通信,跨進(jìn)程訪問等地方。
比如開發(fā)中常用到的 ORM
框架 Mybatis
,或者 JPA
都是需要將實(shí)體類實(shí)現(xiàn)序列化接口才能使用,再者如緩存,緩存的對(duì)象如果沒有實(shí)現(xiàn) Serializable
接口,那么會(huì)拋出異常。
注意事項(xiàng)
父類實(shí)現(xiàn)了序列化,則子類自動(dòng)實(shí)現(xiàn)了序列化,即子類不需要顯式實(shí)現(xiàn) Serializable
接口。
當(dāng)父類沒有實(shí)現(xiàn)序列化,而子類需要實(shí)現(xiàn)時(shí),子類需要顯式實(shí)現(xiàn) Serializable
接口,并且父類中需要有無參的構(gòu)造函數(shù)。
序列化只對(duì)對(duì)象的屬性進(jìn)行保存,而不會(huì)保存其方法。
當(dāng)類中的實(shí)例變量引用了其他對(duì)象,那么在對(duì)該類進(jìn)行序列化時(shí),引用的對(duì)象也會(huì)被序列化。
Java 序列化的缺點(diǎn)
序列化會(huì)讓類變得不靈活。
實(shí)現(xiàn)序列化之后,會(huì)有一個(gè)序列化 ID ,我們可以使用默認(rèn)的 ID ,也可以重寫這 ID,如果沒有顯式指定該序列 ID ,系統(tǒng)會(huì)經(jīng)過一系列復(fù)雜的計(jì)算算出該 ID,那當(dāng)我們改變類中的方法時(shí),這個(gè) ID 就會(huì)變化,這時(shí)候往往就會(huì)報(bào) InvalidClassException
異常。
序列化可以重構(gòu),存在安全隱患。
無法跨語言,Java 進(jìn)行序列化,別的語言無法進(jìn)行反序列化。
序列化后的碼流太大。
Java 序列化性能較低。
舉例說明
父類實(shí)現(xiàn)了序列化,則子類自動(dòng)實(shí)現(xiàn)了序列化,即子類不需要顯式實(shí)現(xiàn) Serializable
接口
Parent.java
public class Parent implements Serializable {
int age;
public Parent(int age)
{
this.age = age;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
@Override public String toString()
{
return "Parent{" + "age=" + age + '}'; }
}
Children.java
public class Children extends Parent {
public Children(int age)
{
super(age);
}
public void say()
{
System.out.println("Hello World " + age);
}
@Override public String toString()
{
return "Children{" + "} " + super.toString(); }
}
Test.java
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 持久化到文件中 Children children = new Children(12);
FileOutputStream outputStream = new FileOutputStream("test.txt"); ObjectOutputStream objectOutputStream = new
ObjectOutputStream(outputStream); objectOutputStream.writeObject(children);
outputStream.close();
objectOutputStream.close();
// 從文件中讀取 FileInputStream inputStream = new FileInputStream("test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Children o = (Children) objectInputStream.readObject();
o.say();
inputStream.close();
objectInputStream.close();
System.out.println(o);
// new File("test.txt").delete(); }
}
控制臺(tái)打印出

在這個(gè)例子中,父類 Parent實(shí)現(xiàn)了 Serializable接口,子類序列化時(shí)并不需要顯式實(shí)現(xiàn) Serializable。當(dāng)父類沒有實(shí)現(xiàn)序列化,而子類需要實(shí)現(xiàn)時(shí),子類需要顯式實(shí)現(xiàn) Serializable接口,并且父類中需要有無參的構(gòu)造函數(shù)。
如果將 Parent改寫,不實(shí)現(xiàn)序列化,讓子類自己來實(shí)現(xiàn)會(huì)怎樣呢?
Parent.java
public class Parent { int age; public Parent(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Parent{" + "age=" + age + '}'; }}
Children.java
public class Children extends Parent implements Serializable{ public Children(int age) { super(age); } public void say() { System.out.println("Hello World " + age); } @Override public String toString() { return "Children{" + "} " + super.toString(); }}
然后運(yùn)行 Test.java

序列化會(huì)讓類變得不靈活。
繼續(xù)改寫上述代碼(注意:我們上述代碼沒有重寫 serialVersionUID),我們先將該 Children保存到文件中,然后再對(duì) Children進(jìn)行修改,修改之后,再?gòu)奈募邪褎偛疟4娴淖止?jié)碼反序列化,看看會(huì)出現(xiàn)什么問題。步驟:
運(yùn)行 Test.java
改寫 Children.java,新增字段 name,并改寫 say()方法注釋掉 Test.java
中的序列化方法,執(zhí)行其中的反序列化
改寫后的 Children.java
public class Children extends Parent {
private String name; public String getName()
{
return name;
}
public void setName(String name) {
this.name = name;
}
public Children(int age) {
super(age);
}
public void say() {
System.out.println("Hello World " + age + ",name: " + name);
}
@Override public String toString() {
return "Children{" + "name='" + name + '\'' + "} " + super.toString(); }
}
控制臺(tái)打印出:

為了解決這個(gè)問題,我們需要在 Children.java中重寫序列化 ID在 Children.java
加入 private static final long serialVersionUID = -1;
即可。
序列化只對(duì)對(duì)象的屬性進(jìn)行保存,而不會(huì)保存其方法。****序列化可以重構(gòu),存在安全隱患。
然后重新運(yùn)行按照上訴步驟重新執(zhí)行一遍,會(huì)發(fā)現(xiàn)不會(huì)報(bào)異常了,并且會(huì)發(fā)現(xiàn)當(dāng)我們?cè)谶\(yùn)行時(shí)期改變 Children
中的 say()方法時(shí),打印出的 say() 方法變了,而 Children中的屬性 age
不會(huì)改變,且會(huì)發(fā)現(xiàn) name默認(rèn)為 null,這里在運(yùn)行時(shí)我們重構(gòu)了 Children
類,改變了一些屬性及方法,這也就存在了安全隱患。當(dāng)類中的實(shí)例變量引用了其他對(duì)象,那么在對(duì)該類進(jìn)行序列化時(shí),引用的對(duì)象也會(huì)被序列化。如果該引用的對(duì)象沒有實(shí)例化,則不需要序列化。
創(chuàng)建一個(gè) XiaoMing類, 改寫 Children ,讓 Children包含 XiaoMing的引用,并且在 Children的構(gòu)造函數(shù)中初始化 XiaoMing,然后運(yùn)行 Test.java。
控制臺(tái)打印出:

沒有實(shí)現(xiàn)序列化接口,這說明在序列化對(duì)象的時(shí)候,對(duì)象的引用對(duì)象也會(huì)被序列化。