1.背景
某天,我在寫代碼定義 bean 的時候,順手寫了個 public class User implements Serializable,旁邊的小哥哥看到了問我:你為什么要實現(xiàn) Serializable 接口?你哪里用到它了嗎?不實現(xiàn)這個接口可以嗎?
emmm,皺眉沉思一下,好像也可以?
好吧,那先來了解一下 Serializable 接口涉及到的相關(guān)概念。
2.序列化協(xié)議+序列化和反序列化
- 序列化是指:將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成特定的格式,使其可以在網(wǎng)絡(luò)中傳輸,或可存儲在內(nèi)存/文件中。
序列化后的數(shù)據(jù)必須是可保持或可傳輸?shù)臄?shù)據(jù)格式,例如:二進(jìn)制串/字節(jié)流、XML、JSON等。
- 反序列化:是序列化的逆過程,將對象從序列化數(shù)據(jù)中還原出來。
自問自答
- 問:序列化的目的是什么?
- 答:方便的進(jìn)行數(shù)據(jù)的交換和傳輸工作。
3.JDK類庫中的序列化API
Java本身提供了對數(shù)據(jù)/對象序列化的支持。
-
輸入輸出流
-
ObjectOutputStream對象輸出流,其writeObject(Object obj)方法可對參數(shù)指定的 obj 對象進(jìn)行序列化,把得到的字節(jié)序列寫到一個目標(biāo)輸出流中。 -
ObjectInputStream對象輸入流,其readObject()方法從一個源輸入流中讀取字節(jié)序列,再把它們反序列化為一個對象,并將其返回。
-
-
接口
- 只有實現(xiàn)了
Serializable和Externalizable接口的類的對象才能被序列化。 -
Externalizable接口繼承自Serializable接口。 - 實現(xiàn)
Externalizable接口的類完全由自身來控制序列化的行為;而僅實現(xiàn)Serializable接口的類可以采用默認(rèn)的序列化方式 。
- 只有實現(xiàn)了
-
對象序列化步驟:
- 創(chuàng)建一個對象輸出流。
- 通過對象輸出流的
writeObject()方法寫對象。
-
對象反序列化步驟:
- 創(chuàng)建一個對象輸入流。
- 通過對象輸入流的
readObject()方法讀取對象。
3.1 對象序列化到文件
// User.java
package com.ann.javas.javacores.serialization.demo1;
import java.io.Serializable;
public class User implements Serializable{
private static String HH="我是靜態(tài)變量,我不會被序列化";
private int userId;
private String userName;
private String address;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public static String getHH() {
return HH;
}
public static void setHH(String HH) {
User.HH = HH;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", address='" + address + '\'' +
'}';
}
}
// Client.java
package com.ann.javas.javacores.serialization.demo1;
import java.io.*;
public class Client {
public static void main(String[] args) throws Exception {
toFile();
fromFile();
}
// Object -> 文件
public static void toFile() throws Exception {
User user = new User();
user.setUserId(1223);
user.setUserName("令習(xí)習(xí)習(xí)");
user.setAddress("北京");
System.out.println("對象:"+user.toString());
System.out.println("對象中的靜態(tài)變量:"+user.getHH());
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
oo.writeObject(user);
System.out.println("序列化成功");
oo.close();
}
// 文件 -> Object
public static void fromFile() throws Exception{
User tmp = new User();
tmp.setHH("我是靜態(tài)變量,我的值是存在JVM靜態(tài)存儲區(qū)的,不是反序列化來的");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
System.out.println("反序列化成功");
User user = (User) ois.readObject();
System.out.println("對象:"+user.toString());
System.out.println("對象中的靜態(tài)變量:"+user.getHH());
ois.close();
}
}
運行結(jié)果:
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
對象中的靜態(tài)變量:我是靜態(tài)變量,我不會被序列化
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
對象中的靜態(tài)變量:我是靜態(tài)變量,我的值是存在JVM靜態(tài)存儲區(qū)的,不是反序列化來的
這是一個簡單的序列化和反序列化例子,創(chuàng)建一個
User實例,將其全部數(shù)據(jù)序列化到文件;然后再從文件讀取數(shù)據(jù)反序列化為對象。需要特別關(guān)注的是:對象序列化保存的是對象的"狀態(tài)",即它的成員變量。因此,對象序列化不會關(guān)注類中的靜態(tài)變量。
3.2 隱藏指定字段
在某些場景下,你希望某些字段不要被序列化,此時可以使用 transient 關(guān)鍵字來進(jìn)行排除。
-
transient關(guān)鍵字只修飾變量,不修飾方法和類。 - 被
transient關(guān)鍵字修飾的變量不再能被序列化,自然也不會被反序列化回來。
// User.java
package com.ann.javas.javacores.serialization.demo2;
import java.io.Serializable;
public class User implements Serializable{
private int userId;
private String userName;
private transient String address;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", address='" + address + '\'' +
'}';
}
}
//Client.java
package com.ann.javas.javacores.serialization.demo2;
import java.io.*;
public class Client {
public static void main(String[] args) throws Exception {
toFile();
fromFile();
}
// Object -> 文件
public static void toFile() throws Exception {
User user = new User();
user.setUserId(1223);
user.setUserName("令習(xí)習(xí)習(xí)");
user.setAddress("北京");
System.out.println("對象:"+user.toString());
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
oo.writeObject(user);
System.out.println("序列化成功");
oo.close();
}
// 文件 -> Object
public static void fromFile() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
System.out.println("反序列化成功");
User user = (User) ois.readObject();
System.out.println("對象:"+user.toString());
ois.close();
}
}
運行結(jié)果:
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='null'}
這里使用 transient 修飾了 User 的 address 變量,因此address不會被序列化,也不會被反序列化。
自問自答
- 問:使用
transient修飾的變量,就一定不會被序列化了嗎?- 答:不一定,要取決于你的程序是怎么寫的。
3.3 Serializable 的 readObject 和 writeObject
// User.java
package com.ann.javas.javacores.serialization.demo3;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class User implements Serializable{
private int userId;
private String userName;
private transient String address;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", address='" + address + '\'' +
'}';
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(address);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
address = (String)in.readObject();
}
}
// Client.java
package com.ann.javas.javacores.serialization.demo3;
import java.io.*;
public class Client {
public static void main(String[] args) throws Exception {
toFile();
fromFile();
}
// Object -> 文件
public static void toFile() throws Exception {
User user = new User();
user.setUserId(1223);
user.setUserName("令習(xí)習(xí)習(xí)");
user.setAddress("北京");
System.out.println("對象:"+user.toString());
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
oo.writeObject(user);
System.out.println("序列化成功");
oo.close();
}
// 文件 -> Object
public static void fromFile() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
System.out.println("反序列化成功");
User user = (User) ois.readObject();
System.out.println("對象:"+user.toString());
ois.close();
}
}
運行結(jié)果:
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
在這個例子中,User 定義了兩個private方法:readObject() 和 writeObject() 。
在 writeObject() 方法中會先調(diào)用 ObjectOutputStream 中的 defaultWriteObject() 方法,該方法會執(zhí)行默認(rèn)的序列化機制,此時會忽略掉被 transient 修飾的address字段。然后再調(diào)用 writeObject() 方法顯示地將address字段寫入到 ObjectOutputStream 中。
readObject() 的作用則是針對對象的讀取,其原理與 writeObject() 方法相同。
3.4 實現(xiàn) Externalizable 接口
在Java中,對象的序列化可以通過實現(xiàn)兩種接口來實現(xiàn):
- 若實現(xiàn)的是
Serializable接口,則所有的序列化將會自動進(jìn)行,如果你希望在此基礎(chǔ)之上加點自定義的內(nèi)容,就可以像上面那樣加兩個方法就ok了。 - 若實現(xiàn)的是
Externalizable接口,則沒有任何東西可以自動序列化,需要在
writeExternal()方法中進(jìn)行手工指定所要序列化的變量,以及如何序列化,這與是否被transient修飾無關(guān)(也就是說,當(dāng)你不需要java自動為你序列化的時候,transient就失效了);當(dāng)然readExternal()也需要做相應(yīng)的處理。
// User.java
package com.ann.javas.javacores.serialization.demo3;
import java.io.*;
public class User implements Externalizable{
private int userId;
private String userName;
private transient String address;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", address='" + address + '\'' +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(userId + 122);
out.writeObject(userName);
out.writeObject(address);
System.out.println("writeExternal:我沒有存原文哦");
out.flush();
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
userId = in.readInt();
userName = (String)in.readObject();
address = (String)in.readObject();
}
}
// Client.java
package com.ann.javas.javacores.serialization.demo3;
import java.io.*;
public class Client {
public static void main(String[] args) throws Exception {
toFile();
fromFile();
}
// Object -> 文件
public static void toFile() throws Exception {
User user = new User();
user.setUserId(1223);
user.setUserName("令習(xí)習(xí)習(xí)");
user.setAddress("北京");
System.out.println("對象:"+user.toString());
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
oo.writeObject(user);
System.out.println("序列化成功");
oo.close();
}
// 文件 -> Object
public static void fromFile() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
System.out.println("反序列化成功");
User user = (User) ois.readObject();
System.out.println("對象:"+user.toString());
ois.close();
}
}
運行結(jié)果:
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
writeExternal:我沒有存原文哦
序列化成功
反序列化成功
對象:User{userId=1345, userName='令習(xí)習(xí)習(xí)', address='北京'}
這里有幾個關(guān)鍵單需要說明:
- 實現(xiàn)
Externalizable接口,一定要自定義序列化方法,如果你把writeExternal()和readExternal()這里面的實現(xiàn)都丟掉,就會發(fā)現(xiàn),java真的什么都不會做。 - 如上面所說,當(dāng)實現(xiàn)了
Externalizable接口的時候,transient關(guān)鍵字不再生效。 - 反序列化時,實際上調(diào)用了
User的無參構(gòu)造函數(shù),因此在自定義序列化方案的時候,請一定要記得提供一個 公共無參構(gòu)造函數(shù) ,不然就悲劇了。
4.關(guān)于 Serializable 和 Externalizable 的總結(jié)和附加說明
-
構(gòu)造器:
-
Serializable序列化時不會調(diào)用默認(rèn)的構(gòu)造器; - 而
Externalizable序列化時會調(diào)用默認(rèn)構(gòu)造器。
-
-
功能
- 一個對象想要被序列化,那么它的類就要實現(xiàn)
Serializable接口,這個對象的所有屬性(包括private屬性、包括其引用的對象)都可以被序列化和反序列化來保存、傳遞。 -
Externalizable是Serializable接口的子類,有時我們不希望序列化那么多,可以使用這個接口,這個接口的writeExternal()和readExternal()方法可以指定序列化哪些屬性。
- 一個對象想要被序列化,那么它的類就要實現(xiàn)
-
關(guān)鍵字
- 由于
Externalizable對象默認(rèn)不保存對象的任何字段,所以transient關(guān)鍵字只能伴隨Serializable使用,雖然Externalizable對象中使用transient關(guān)鍵字也不報錯,但不起任何作用。
- 由于
-
方法
-
Serializable接口的writeObject()和readObject()方法是可選實現(xiàn),若沒有自定義,則使用默認(rèn)的。 -
Externalizable接口的writeExternal()和readExternal()方法是必選實現(xiàn),當(dāng)然你可以在里面什么都不做。
-
自問自答
- 問:
writeObject()和readObject()都是private方法,它們是怎么被調(diào)用的呢?- 答:很顯然,反射。詳情可見
ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。