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ù)格式,例如:二進制串/字節(jié)流磨取、XML人柿、JSON等。
- 反序列化:是序列化的逆過程忙厌,將對象從序列化數(shù)據(jù)中還原出來凫岖。
自問自答
- 問:序列化的目的是什么?
- 答:方便的進行數(shù)據(jù)的交換和傳輸工作逢净。
3.JDK類庫中的序列化API
Java本身提供了對數(shù)據(jù)/對象序列化的支持哥放。
-
輸入輸出流
-
ObjectOutputStream
對象輸出流,其writeObject(Object obj)
方法可對參數(shù)指定的 obj 對象進行序列化爹土,把得到的字節(jié)序列寫到一個目標輸出流中甥雕。 -
ObjectInputStream
對象輸入流,其readObject()
方法從一個源輸入流中讀取字節(jié)序列胀茵,再把它們反序列化為一個對象社露,并將其返回。
-
-
接口
- 只有實現(xiàn)了
Serializable
和Externalizable
接口的類的對象才能被序列化琼娘。 -
Externalizable
接口繼承自Serializable
接口峭弟。 - 實現(xiàn)
Externalizable
接口的類完全由自身來控制序列化的行為赁濒;而僅實現(xiàn)Serializable
接口的類可以采用默認的序列化方式 。
- 只有實現(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)鍵字來進行排除。
-
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í)行默認的序列化機制邮破,此時會忽略掉被 transient
修飾的address字段诈豌。然后再調(diào)用 writeObject()
方法顯示地將address字段寫入到 ObjectOutputStream
中仆救。
readObject()
的作用則是針對對象的讀取,其原理與 writeObject()
方法相同矫渔。
3.4 實現(xiàn) Externalizable 接口
在Java中彤蔽,對象的序列化可以通過實現(xiàn)兩種接口來實現(xiàn):
- 若實現(xiàn)的是
Serializable
接口,則所有的序列化將會自動進行庙洼,如果你希望在此基礎(chǔ)之上加點自定義的內(nèi)容顿痪,就可以像上面那樣加兩個方法就ok了。 - 若實現(xiàn)的是
Externalizable
接口油够,則沒有任何東西可以自動序列化蚁袭,需要在
writeExternal()
方法中進行手工指定所要序列化的變量,以及如何序列化石咬,這與是否被transient
修飾無關(guān)(也就是說揩悄,當你不需要java自動為你序列化的時候,transient就失效了)鬼悠;當然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真的什么都不會做袜啃。 - 如上面所說汗侵,當實現(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)用默認的構(gòu)造器起愈; - 而
Externalizable
序列化時會調(diào)用默認構(gòu)造器只恨。
-
-
功能
- 一個對象想要被序列化,那么它的類就要實現(xiàn)
Serializable
接口抬虽,這個對象的所有屬性(包括private屬性官觅、包括其引用的對象)都可以被序列化和反序列化來保存、傳遞阐污。 -
Externalizable
是Serializable
接口的子類休涤,有時我們不希望序列化那么多,可以使用這個接口,這個接口的writeExternal()
和readExternal()
方法可以指定序列化哪些屬性功氨。
- 一個對象想要被序列化,那么它的類就要實現(xiàn)
-
關(guān)鍵字
- 由于
Externalizable
對象默認不保存對象的任何字段序苏,所以transient
關(guān)鍵字只能伴隨Serializable
使用,雖然Externalizable
對象中使用transient
關(guān)鍵字也不報錯捷凄,但不起任何作用忱详。
- 由于
-
方法
-
Serializable
接口的writeObject()
和readObject()
方法是可選實現(xiàn),若沒有自定義跺涤,則使用默認的匈睁。 -
Externalizable
接口的writeExternal()
和readExternal()
方法是必選實現(xiàn),當然你可以在里面什么都不做钦铁。
-
自問自答
- 問:
writeObject()
和readObject()
都是private方法软舌,它們是怎么被調(diào)用的呢?- 答:很顯然牛曹,反射佛点。詳情可見
ObjectOutputStream
中的writeSerialData
方法,以及ObjectInputStream
中的readSerialData
方法黎比。