java對象是有生命周期的,當生命周期結束它就會被回收,但是可以通過將其轉換為字節(jié)序列永久保存下來或者通過網絡傳輸給另一方。
把對象轉換為字節(jié)序列的過程稱為對象的序列化;把字節(jié)序列恢復為對象的過程稱為對象的反序列化。
serializable接口
一個類實現(xiàn)java.io.serializable接口就可以被序列化或者反序列化。實際上,serializable接口中沒有任何變量和方法,它只是一個標識。如果沒有實現(xiàn)這個接口,在序列化或者反序列化時會拋出notserializableexception異常。
下面是一個實現(xiàn)了serializable接口的類以及它的序列化與反序列化過程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public class serialtest { public static void main(string[] args) { test test = new test(); test.setname( "test" ); // 序列化,存儲對象到文本 objectoutputstream oos = null ; try { oos = new objectoutputstream( new fileoutputstream( "test" )); oos.writeobject(test); } catch (ioexception e) { e.printstacktrace(); } finally { try { if (oos != null ) { oos.close(); } } catch (ioexception e) { e.printstacktrace(); } } // 反序列化,從文本中取出對象 objectinputstream ois = null ; try { ois = new objectinputstream( new fileinputstream( "test" )); test1 test1 = (test1) ois.readobject(); } catch (ioexception e) { e.printstacktrace(); } catch (classnotfoundexception e) { e.printstacktrace(); } finally { try { if (ois != null ) { ois.close(); } } catch (ioexception e) { e.printstacktrace(); } } } } class test implements serializable { private string name; public string getname() { return name; } public void setname(string name) { this .name = name; } @override public string tostring() { return "test{" + "name='" + name + '\ '' + '}' ; } } |
運行結果:
test{name='test'}
serialversionuid
1
|
private static final long serialversionuid = -3297006450515623366l; |
serialversionuid是一個序列化版本號,實現(xiàn)serializable接口的類都會有一個版本號。如果沒有自己定義,那么程序會默認生成一個版本號,這個版本號是java運行時環(huán)境根據(jù)類的內部細節(jié)自動生成的。最好我們自己定義該版本號,否則當類發(fā)生改變時,程序為我們自動生成的序列化版本號也會發(fā)生改變,那么再將原來的字節(jié)序列反序列化時就會發(fā)生錯誤。
下面是將test1類加入一個變量age,此時再進行反序列化的結果。可以看出,序列化版本號已發(fā)生改變,程序認為不是同一個類,不能進行反序列化。
1
2
3
4
5
6
7
8
|
java.io.invalidclassexception: test.test1; local class incompatible: stream classdesc serialversionuid = 9097989105451761251 , local class serialversionuid = - 7756223913249050270 at java.base/java.io.objectstreamclass.initnonproxy(objectstreamclass.java: 689 ) at java.base/java.io.objectinputstream.readnonproxydesc(objectinputstream.java: 1903 ) at java.base/java.io.objectinputstream.readclassdesc(objectinputstream.java: 1772 ) at java.base/java.io.objectinputstream.readordinaryobject(objectinputstream.java: 2060 ) at java.base/java.io.objectinputstream.readobject0(objectinputstream.java: 1594 ) at java.base/java.io.objectinputstream.readobject(objectinputstream.java: 430 ) at test.serialtest.main(serialtest.java: 11 ) |
為了提高serialversionuid的獨立性和確定性,強烈建議在一個可序列化類中顯示地定義serialversionuid,為他賦予明確的值。
那么在idea中,怎么手動生成呢?
在settings->editor->inspections下,搜索serial,開啟serializable class without 'serialversionuid'的拼寫檢查,然后將光標放在實現(xiàn)serializable的接口上,按住alt+enter鍵,選擇添加serialversionuid即可。
transient關鍵字
transient修飾類的變量,可以使變量不被序列化。反序列化時,被transient修飾的變量的值被設為初始值,如int類型被設為0,對象型被設為null。
objectoutputstream類和objectinputstream類
objectoutputstream的writeobject方法可以序列化對象,objectinputstream的readobject可以反序列化對象。objectoutputstream實現(xiàn)了接口objectoutput,所以可以進行對象寫操作。objectinputstream實現(xiàn)了接口objectinput,所以可以對對象進行讀操作。
靜態(tài)變量序列化
給test類中增加一個靜態(tài)變量,賦值為12,然后在序列化之后修改其值為10,反序列化之后打印它的值。發(fā)現(xiàn)打印的值為10,之前的12并沒有被保存。
靜態(tài)變量是不參與序列化的,序列化只是用來保存對象的狀態(tài),而靜態(tài)變量屬于類的狀態(tài)。
父類序列化
讓test繼承一個沒有實現(xiàn)serializable接口的類,設置父類中變量的值,對test類的實例進行序列化與反序列化操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
public class serialtest { public static void main(string[] args) { test test = new test(); test.setname( "huihui" ); test.setsex( 12 ); // 序列化,存儲對象到文本 objectoutputstream oos = null ; try { oos = new objectoutputstream( new fileoutputstream( "test" )); oos.writeobject(test); } catch (ioexception e) { e.printstacktrace(); } finally { try { if (oos != null ) { oos.close(); } } catch (ioexception e) { e.printstacktrace(); } } // 反序列化,從文本中取出對象 objectinputstream ois = null ; try { ois = new objectinputstream( new fileinputstream( "test" )); test test1 = (test) ois.readobject(); system.out.println(test1); } catch (ioexception | classnotfoundexception e) { e.printstacktrace(); } finally { try { if (ois != null ) { ois.close(); } } catch (ioexception e) { e.printstacktrace(); } } } } class test extends testfather implements serializable { private static final long serialversionuid = 4335715933640891747l; private string name; public string getname() { return name; } public void setname(string name) { this .name = name; } @override public string tostring() { return "test{" + "name='" + name + '\ '' + "sex='" + sex + '\ '' + '}' ; } } class testfather { protected integer sex; public integer getsex() { return sex; } public void setsex(integer sex) { this .sex = sex; } @override public string tostring() { return "testfather{" + "sex='" + sex + '\ '' + '}' ; } } |
運行結果:
test{name='huihui'sex='null'}
發(fā)現(xiàn)雖然對sex進行了復制,但是反序列化結果仍然為null。
現(xiàn)在讓testfather類實現(xiàn)serializable接口,運行結果如下。所以當我們想要序列化父類的變量時,也需要讓父類實現(xiàn)serializable接口。
1
|
test{name= 'huihui' sex= '12' } |
同理,如果test類中有任何變量是對象,那么該對象的類也需要實現(xiàn)serializable接口。查看string源代碼,確實實現(xiàn)了serializable接口。大家可以測試一下字段的類不實現(xiàn)serializable接口的情況,運行會直接報java.io.notserializableexception異常。
敏感字段加密
如果對于某些字段我們并不想直接暴露出去,需要對其進行加密處理,那么就需要我們自定義序列化和反序列化方法。使用serializable接口進行序列化時,如果不自定義方法,則默認調用objectoutputstream的defaultwriteobject方法和objectinputstream的defaultreadobject方法。下面我們來嘗試一下自己實現(xiàn)序列化與反序列化過程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
class test implements serializable { private static final long serialversionuid = 4335715933640891747l; private string name; public string getname() { return name; } public void setname(string name) { this .name = name; } @override public string tostring() { return "test{" + "name='" + name + '\ '' + '}' ; } private void writeobject(objectoutputstream out) { try { objectoutputstream.putfield putfield = out.putfields(); system.out.println( "原name:" + name); // 模擬加密 name = "change" ; putfield.put( "name" , name); system.out.println( "加密后的name:" + name); out.writefields(); } catch (ioexception e) { e.printstacktrace(); } } private void readobject(objectinputstream in) { try { objectinputstream.getfield getfield = in.readfields(); object object = getfield.get( "name" , "" ); system.out.println( "要解密的name:" + object.tostring()); name = "huihui" ; } catch (ioexception e) { e.printstacktrace(); } catch (classnotfoundexception e) { e.printstacktrace(); } } } |
運行結果:
原name:huihui
加密后的name:change
要解密的name:change
解密后的name:huihui
這種寫法重寫了writeobject方法和readobject方法,下面一種接口也可以實現(xiàn)相同的功能。
externalizable接口
除了serializable接口,java還提供了一個externalizable接口,它繼承了serializable接口,提供了writeexternal和readexternal兩個方法,實現(xiàn)該接口的類必須重寫這兩個方法。同時還發(fā)現(xiàn),類還必須提供一個無參構造方法,否則會報java.io.invalidclassexception異常。
先不深究為什么要加一個無參構造方法,我們先試一下這個接口的序列化效果。將類test改為如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class test implements externalizable { private static final long serialversionuid = 4335715933640891747l; private string name; public test() { } public string getname() { return name; } public void setname(string name) { this .name = name; } @override public string tostring() { return "test{" + "name='" + name + '\ '' + '}' ; } @override public void writeexternal(objectoutput out) throws ioexception { } @override public void readexternal(objectinput in) throws ioexception, classnotfoundexception { } } |
再次運行測試方法,發(fā)現(xiàn)輸出的name是null。在readobject處打斷點,發(fā)現(xiàn)會調用無參構造方法。
name其實并沒有被序列化與反序列化,writeexternal方法和readexternal方法中是需要我們自己來實現(xiàn)序列化與反序列化的細節(jié)的。在反序列化時,會首先調用類的無參考構造方法創(chuàng)建一個新對象,然后再填充每個字段。
我們對writeexternal方法和readexternal方法進行重寫:
1
2
3
4
5
6
7
8
|
@override public void writeexternal(objectoutput out) throws ioexception { out.writeobject(name); } @override public void readexternal(objectinput in) throws ioexception, classnotfoundexception { name = (string) in.readobject(); } |
此時運行測試方法,發(fā)現(xiàn)test類被正常序列化與反序列化。
序列化存儲規(guī)則
當多次序列化一個對象時,是會序列化多次還是會序列化一次呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
public class serialtest { public static void main(string[] args) { test test = new test(); test.setname( "huihui" ); // 序列化,存儲對象到文本 objectoutputstream oos = null ; try { oos = new objectoutputstream( new fileoutputstream( "test" )); // 兩次寫入文件 oos.writeobject(test); oos.flush(); system.out.println( new file( "test" ).length()); oos.writeobject(test); oos.flush(); system.out.println( new file( "test" ).length()); } catch (ioexception e) { e.printstacktrace(); } finally { try { if (oos != null ) { oos.close(); } } catch (ioexception e) { e.printstacktrace(); } } // 反序列化,從文本中取出對象 objectinputstream ois = null ; try { ois = new objectinputstream( new fileinputstream( "test" )); // 讀取兩個對象 test test1 = (test) ois.readobject(); test test2 = (test) ois.readobject(); system.out.println(test1 == test1); } catch (ioexception | classnotfoundexception e) { e.printstacktrace(); } finally { try { if (ois != null ) { ois.close(); } } catch (ioexception e) { e.printstacktrace(); } } } } class test implements serializable { private static final long serialversionuid = 4335715933640891747l; private string name; public string getname() { return name; } public void setname(string name) { this .name = name; } @override public string tostring() { return "test{" + "name='" + name + '\ '' + '}' ; } } |
運行結果:
73
78
true
可以發(fā)現(xiàn),當?shù)诙螌懭雽ο髸r,文件的長度僅僅增加了5個字節(jié),并且在判等時,兩個引用指向同一地址。
java序列化機制為了節(jié)省磁盤空間,具有特定的存儲規(guī)則,當寫入文件為同一對象時,并不會再將對象的內容進行存儲,而只是再次存儲一份引用。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。如果你想了解更多相關內容請查看下面相關鏈接
原文鏈接:https://blog.csdn.net/sinat_28394909/article/details/84956158