static字段不會被序列化
//這是父類
class Data implements Serializable{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
//這是子類煌往,繼承父類自動繼承Serializable接口
class sub extends Data{
private static int m = 6;
private int k = 10;
public sub(int n) {
super(n);
}
}
父類Data有一個n的屬性,通過構(gòu)造器賦值
子類sub有額外的靜態(tài)屬性m,非靜態(tài)屬性k=10
類 | 父類屬性 | 子類屬性 |
---|---|---|
父類Data | n | |
子類sub | 繼承來的n | 靜態(tài)m=6,非靜態(tài)k=10 |
假設(shè)我們構(gòu)造一個子類對象 new sub(5);
那么當(dāng)前子類對象的屬性為
類 | 父類屬性 | 子類屬性 |
---|---|---|
父類Data | n = 5 | |
子類sub | 繼承來的n=5 | 靜態(tài)m=6,非靜態(tài)k=10 |
按照序列化的結(jié)論刽脖,靜態(tài)屬性不會被序列化羞海,那么猜測序列化再反序列化之后屬性應(yīng)該為
類 | 父類屬性 | 子類屬性 |
---|---|---|
父類Data | n | |
子類sub | 繼承來的n(未初始化,默認(rèn)為0) | 靜態(tài)m=6,非靜態(tài)k=10 |
這里我省略了序列化和反序列化的過程曲管,直接上代碼結(jié)果
這里只有k 和 n 因為 n沒有初始化 所以不顯示却邓,所以第一個結(jié)論驗證成功,這是當(dāng)然的,因為序列化只是為了序列化對象的狀態(tài)
tip:如果在一個JVM存活周期內(nèi)院水,在本地JVM進行序列化和反序列化腊徙,是有可能讀到n=10時的,這個n=10不是序列化得到的檬某,而是JVM在方法區(qū)找到new出來對象時的n
反序列化什么時候需要一個空參構(gòu)造器
這部分篇幅較長撬腾,涉及到源碼部分,有興趣的可以查看我的另一篇文章
從源碼解析JAVA序列化是否需要空參構(gòu)造方法
序列化會遞歸域?qū)ο蟮男蛄谢?/h3>
這個驗證看起來可能會有點吃力恢恼,我用的《thinking in java》上的例子
class Data implements Serializable{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
public class Worm implements Serializable{
private static Random rand = new Random(47);
private Data[] d = {
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10))
};
private Worm next;
private char c;
public Worm(int i,char x){
System.out.println("Worm constructor: " + i);
c = x;
if(-- i > 0){
next = new Worm(i , (char)(x+1));
}
}
public Worm(){
System.out.println("default constructor");
}
public String toString(){
StringBuilder result = new StringBuilder(":");
result.append(c);
result.append("(");
for(Data dat : d)
result.append(dat);
result.append(")");
if(next != null)
result.append(next);
return result.toString();
}
//序列化和反序列化調(diào)用的方法入口
public static void main(String[] args) throws IOException, ClassNotFoundException {
Worm w =new Worm(6,'a');
System.out.println("w = " + w);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
out.writeObject("Worm storage\n");
out.writeObject(w);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
String s = (String)in.readObject();
Worm w2 = (Worm) in.readObject();
System.out.println(s + "w2 = " + w2);
}
}
簡單描述一下民傻,Worm(蠕蟲)類似一個鏈表的結(jié)構(gòu),有一個next指針指向下一個Worm,而每一個Worm有三個Date是隨機生成的场斑,toString方法會將Worm及Worm鏈接的next都打印出來漓踢,這里我們指定new Worm(6);
使用調(diào)試工具
序列化前的worm,有6個節(jié)點,每個節(jié)點的data都是隨機生成的漏隐,而序列化會保存域的對象喧半,已經(jīng)遞歸的序列化的對象,那么我們猜測反序列化回來的w應(yīng)該也是同樣的結(jié)構(gòu),直接上結(jié)果
w2是反序列化回來的結(jié)果锁保,可以看出不僅對象的域相同薯酝,對象的next對象都同樣序列化了
不指定序列號,編譯器會自動生成一個UID
還是用老朋友爽柒,data類
class Data implements Serializable{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
可以看到?jīng)]有指定序列號吴菠,沒有指定序列號的時候,編譯器會自動根據(jù)Class哈希出一個UID浩村,這樣只要對類稍微改動則版本就會發(fā)生改變
Data w = new Data(10);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
out.writeObject(w);
out.close();
這里用舊Data類持久化一個對象做葵,然后我們對Data類稍微小改造,增加一個字段k
class Data implements Serializable{
private int k;//新增加的字段
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
按照推測心墅,這個Data的UID已經(jīng)發(fā)生變化酿矢,反序列化時因為UID與持久化對象的UID不同,會丟出InvalidClassException異常而反序列失敗怎燥,我們上結(jié)果
Exception in thread "main" java.io.InvalidClassException: main.Data; local class incompatible: stream classdesc serialVersionUID = -1762858249998764225, local class serialVersionUID = -2944979104297389356
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at main.Worm.main(Worm.java:76)
驗證成功了瘫筐,說明確實編譯器會為沒有UID的對象根據(jù)類文件自動哈希一個UID,以便接受方驗證類版本是否發(fā)生變化铐姚,但是不推薦不注明UID策肝,因為自動編譯的UID不可控肛捍,可能兩個類文件結(jié)構(gòu)相同,但是格式具有區(qū)別节槐,或者是平臺的區(qū)別装处,導(dǎo)致UID不同序列化失敗遗遵,因此我們應(yīng)該自己維護一個UID叉趣,并且每次對類修改時對UID進行更新。
指定類版本UID桦山,但發(fā)送方和接收方類UID不相同時尘分,可以序列化
序列化和反序列化時只會根據(jù)UID來驗證類的一致性排拷,因此有趣的是膘婶,即使序列化和反序列化的類文件結(jié)構(gòu)不同缺前,但是只要UID一樣,也能夠序列化成功悬襟,但是會丟失數(shù)據(jù)诡延。
我們分情況驗證:
- 序列化的類比反序列化的類多一條字段
還是Data,序列化的Data多出來一個k字段
class Data implements Serializable{
private static final long serialVersionUID = 1L;//這里指定了UID
private int k = 10;//比反序列化的Data多的字段
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
反序列化的Data沒有k
class Data implements Serializable{
private static final long serialVersionUID = 1L;//這里指定了UID
//private int k = 10;
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
然后我們將Data序列化
有兩個字段k=10,n=10;
那么反序列能成功嗎,反序列之后k還存在嗎
這里反序列化成功古胆,并且反序列化之后k被丟棄了,這里我們可以得出結(jié)論:
序列化UID相同時筛璧,即使類版本不同逸绎,也能夠序列化成功,序列化前多出來的字段會被丟棄
- 反序列化的類比序列化的類多一條字段
這里不貼詳細(xì)代碼了夭谤,只要把上面兩個類相反就可以了
最后得出的結(jié)論是:
序列化UID相同時棺牧,即使類版本不同,也能夠序列化成功朗儒,反序列化多出來的字段不會得到賦值
發(fā)送方序列化子類類型颊乘,接受反反序列化用父類也可以接受
舉個例子
class Data implements Serializable{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
class sub extends Data{
private static int m = 6;
private int k;
public sub(int n) {
super(n);
}
}
sub繼承Data,序列化時我將子類sub的一個對象序列化,反序列化時我用父類Data接受,反序列化的結(jié)果會自動向上轉(zhuǎn)型嗎
首先序列化一個子類sub對象k=0,n=10
然后反序列化我會用Data去接收
ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
Data w2 = (Data) in.readObject();//接受序列化的子類Sub對象
序列化成功醉锄,同時通過debug發(fā)現(xiàn)乏悄,即時用Data接收,w2對象仍然是一個sub類型
為什么會這樣呢恳不,我們打開序列化的文件
?í ?sr main.subbê??uéaP? ?I ?kxr main.Data?????t÷?? ?I ?nxp
有亂碼但是不影響我們觀察檩小,我們可以看到序列化時,不僅序列化了對象域的值烟勋,同時會指明序列化的這個對象是什么规求,也會指明繼承Serializable父類是什么,最后反序列化時總是反序列化為指明的對象
繼續(xù)思考卵惦,如果反序列時用Data接收阻肿,那么比較UID時是比較Data的UID和sub的UID還是sub的UID和sub的UID
一定是sub的UID,不然反序列就失敗了
Class文件可以序列化嗎
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement
可以看到Class是繼承了Serializable接口的,毋庸置疑是可以的
異想天開一下沮尿,可不可以通過Class的序列化傳輸一個可序列化的類丛塌,然后傳輸這個類的對象,接受方反序列得到本來沒有的Class,然后通過這個Class反序列化這個類的對象
這里還是Data類
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
out.writeObject(Data.class);
Data d = new Data(10);
out.writeObject(d);
首先把Data.class序列化,然后將Data的對象d也序列化
然后我們將Data注掉
//class Data implements Serializable{
// private int n;
// public Data(int n){
// this.n = n;
// }
// public String toString(){
// return Integer.toString(n);
// }
//}
現(xiàn)在嘗試接受Data.class和Data的對象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
Class date = (Class) in.readObject();
in.readObject();
Exception in thread "main" java.lang.ClassNotFoundException: main.sub
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:628)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at main.Worm.main(Worm.java:76)
發(fā)現(xiàn)是不能序列化的姨伤,這里筆者回頭看了一下Class文件的域哨坪,發(fā)現(xiàn)大多數(shù)都是static和transient,因此序列化Class對象沒有什么意義
那我們就不能通過網(wǎng)絡(luò)傳輸Class了嗎
其實是可以的乍楚,但是需要借助自定義的ClassLoader当编,通過socket傳輸class的字節(jié)碼文件,委托給自定義ClassLoader加載徒溪,就能夠?qū)崿F(xiàn)遠(yuǎn)程類加載