摘要:的序列化是將一個對象表示成字節序列,該字節序列包括了對象的數據,有關對象的類型信息和存儲在對象中的數據類型。任何實現了接口的類都可以被序列化。一旦對象被序列化或者重新裝配,就會分別調用那兩個方法。
Java序列化 1. 什么是序列化?
序列化是將一個對象的狀態,各屬性的值序列化保存起來,然后在合適的時候通過反序列化獲得。
Java的序列化是將一個對象表示成字節序列,該字節序列包括了對象的數據,有關對象的類型信息和存儲在對象中的數據類型。
說白了,就是將對象保存起來,就跟保存字符串數據一樣,用到的時候再取出來。任何實現了Serializable接口的類都可以被序列化。
2. 實現Serializable接口進行序列化package com.wangjun.othersOfJava; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerializeDemo { public static void main(String[] args) { Employee em = new Employee(); em.name = "wangjun"; em.age = 24; em.ssh = 123456; // 將對象序列化后保存到文件 try ( FileOutputStream fo = new FileOutputStream("tem.ser"); ObjectOutputStream oo = new ObjectOutputStream(fo)) { oo.writeObject(em); } catch (IOException e) { e.printStackTrace(); } // 反序列化取出對象 try( FileInputStream fi = new FileInputStream("tem.ser"); ObjectInputStream oi = new ObjectInputStream(fi)) { Employee e2 = (Employee) oi.readObject(); System.out.println(e2.name); System.out.println(e2.age); System.out.println(e2.ssh); System.out.println(Employee.local); e2.test(); } catch (Exception e) { e.printStackTrace(); } } static class Employee implements Serializable { String name; int age; static String local = "earth"; transient int ssh; public void test() { System.out.println("this is test method!"); } } }
程序的運行結果:
wangjun 24 0 earth this is test method!
如果有一些字段不想被序列化怎么辦呢?這時候就可以用transient關鍵字修飾,就像上面代碼的ssh字段,關于transient關鍵字有以下幾個特點:
一旦被transient關鍵字修飾,那變量將不再是對象持久化的一部分,該變量內容在序列化后無法獲得訪問;
transient只能修飾變量,不能修飾方法和類,本地變量(局部變量)也不能被transient修飾;
一個靜態變量不管是否被transient修飾,都不能被序列化。
從上面的例子看到好像與第三條不符,其實反序列化取出的local是JVM里面的值,而不是反序列化出來的。可以加一行代碼驗證一下,在反序列化之前更改一下local的值:
// 反序列化取出對象 Employee.local = "earth2"; try( ...
看一下打印結果
wangjun 24 0 earth2 this is test method!
這說明打印出來的是JVM中對應的local的值earth2,而不是序列化的時候的值earth。
3. 實現Externalizable接口進行序列化transient只有對實現了Serializable接口方式的序列化有效,還有一種序列化的方式是實現Externalizable接口,這種實現方式不像實現Serializable接口一樣可以幫你自動序列化,它需要在writeExternal方法中手動指定需要序列化的變量并且在readExternal手動取出來,這與是否被transient修飾無關,下面更改一下上面的例子,將Employee類改成:
static class Employee implements Externalizable { String name; int age; static String local = "earth"; transient int ssh; //實現Externalizable接口進行序列化必須顯式聲明無參構造器 public Employee() { } public void test() { System.out.println("this is test method!"); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); //out.writeObject(age); out.writeObject(ssh); out.writeObject(local); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); //age = (int) in.readObject(); ssh = (int) in.readObject(); local = (String) in.readObject(); } }
重新運行,結果(注意:上述主函數中還存在對local重新賦值的代碼Employee.local = "earth2";):
wangjun 0 123456 earth this is test method!
可以看到能否被序列化跟transient和static修飾都沒有關系,只跟writeExternal和readExternal有關系。
4. Serializable和Externalizable的區別對Serializable對象反序列化時,由于Serializable對象完全以它存儲的二進制位為基礎來構造,因此并不會調用任何構造函數,因此Serializable類無需默認構造函數,但是當Serializable類的父類沒有實現Serializable接口時,反序列化過程會調用父類的默認構造函數,因此該父類必需有默認構造函數,否則會拋異常。
對Externalizable對象反序列化時,會先調用類的不帶參數的構造方法,這是有別于默認反序列方式的。如果把類的不帶參數的構造方法刪除,或者把該構造方法的訪問權限設置為private、默認或protected級別,會拋出java.io.InvalidException: no valid constructor異常,因此Externalizable對象必須有默認構造函數,而且必需是public的。
如果不是特別堅持實現Externalizable接口,那么還有另一種方法。我們可以實現Serializable接口,并添加writeObject()和readObject()的方法。一旦對象被序列化或者重新裝配,就會分別調用那兩個方法。也就是說,只要提供了這兩個方法,就會優先使用它們,而不考慮默認的序列化機制。
5. SerialVersionUID的作用上述實現Serializable接口的Employee類中,會有一個警告:
The serializable class Employee does not declare a static final serialVersionUID field of type long
意思是Employee沒有聲明一個靜態final的常量serialVersionUID,那這個serialVersionUID的作用是什么呢?
serialVersionUID是對類進行版本控制的,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。
serialVersionUID有兩種生成方式:
一是默認的1L,比如:private static final long serialVersionUID = 1L;
二是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段。
如果程序沒有顯式的聲明serialVersionUID,那么程序將用第二種實現。我們可以做一個實現,還是用上述實現Serializable接口的例子。
我們先運行一下程序,生成序列化文件tem.ser,在把“將對象序列化后保存到文件”這一段邏輯注釋掉,對Employee類增加一個test字段:
static class Employee implements Serializable { String name; int age; static String local = "earth"; transient int ssh; String test; public void test() { System.out.println("this is test method!"); } }
這時候運行的時候會報錯:
java.io.InvalidClassException: com.wangjun.othersOfJava.SerializeDemo$Employee; local class incompatible: stream classdesc serialVersionUID = 4506166831890198488, local class serialVersionUID = 785960679919880606 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) at com.wangjun.othersOfJava.SerializeDemo.main(SerializeDemo.java:32)
因為程序發現取到的序列化文件的serialVersionUID和當前的serialVersionUID不一樣。這個serialVersionUID是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,因為增加了test字段,因此生成的serialVersionUID不一樣了。
接著,我們顯式的聲明serialVersionUID
static class Employee implements Serializable { private static final long serialVersionUID = 1L; String name; int age; static String local = "earth"; transient int ssh; public void test() { System.out.println("this is test method!"); } }
將剛才注釋的代碼取消注釋,運行一遍再注釋掉,并且新增字段test:
static class Employee implements Serializable { private static final long serialVersionUID = 1L; String name; int age; static String local = "earth"; transient int ssh; String test; public void test() { System.out.println("this is test method!"); } }
再次運行發現沒有報錯,運行OK。這是因為你顯式聲明了serialVersionUID,序列化的serialVersionUID和目前的serialVersionUID一樣,因此會認為是同一個版本的類。
你也可以將serialVersionUID改成2L,這個時候又會報錯了。
參考:https://www.cnblogs.com/duanx...
https://blog.csdn.net/fjndwy/...
https://blog.csdn.net/bigtree...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68828.html
摘要:由以上結果分析可知,靜態變量不能被序列化,示例讀取出來的是在內存中存儲的值。關鍵字總結修飾的變量不能被序列化只作用于實現接口只能用來修飾普通成員變量字段不管有沒有修飾,靜態變量都不能被序列化好了,棧長花了半天時間,終于整理完了。 先解釋下什么是序列化 我們的對象并不只是存在內存中,還需要傳輸網絡,或者保存起來下次再加載出來用,所以需要Java序列化技術。 Java序列化技術正是將對象轉...
摘要:對象序列化對象序列化機制允許把內存中的對象轉換成與平臺無關的二進制流,從而可以保存到磁盤或者進行網絡傳輸,其它程序獲得這個二進制流后可以將其恢復成原來的對象。 對象序列化 對象序列化機制允許把內存中的Java對象轉換成與平臺無關的二進制流,從而可以保存到磁盤或者進行網絡傳輸,其它程序獲得這個二進制流后可以將其恢復成原來的Java對象。 序列化機制可以使對象可以脫離程序的運行而對立存在 ...
摘要:在中,對象的序列化與反序列化被廣泛應用到遠程方法調用及網絡傳輸中。相關接口及類為了方便開發人員將對象進行序列化及反序列化提供了一套方便的來支持。未實現此接口的類將無法使其任何狀態序列化或反序列化。 序列化與反序列化 序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。一般將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等。在網絡傳輸過程中,可以...
摘要:的序列化是將一個對象表示成字節序列,該字節序列包括了對象的數據,有關對象的類型信息和存儲在對象中的數據類型。這個是根據類名接口名成員方法及屬性等來生成一個位的哈希字段,因為增加了字段,因此生成的不一樣了。 Java序列化 什么是序列化? 序列化是將一個對象的狀態,各屬性的值序列化保存起來,然后在合適的時候通過反序列化獲得。 Java的序列化是將一個對象表示成字節序列,該字節序列包括了對...
摘要:從的序列化和反序列化說起序列化是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,而相反的過程就稱為反序列化。當使用接口來進行序列化與反序列化的時候需要開發人員重寫與方法。 從java的序列化和反序列化說起 序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,而相反的過程就稱為反序列化。 在java中允許我們創建可復用的對象,但是這些對象僅僅存在j...
閱讀 3686·2021-11-12 10:36
閱讀 3831·2021-09-22 15:48
閱讀 3543·2019-08-30 15:54
閱讀 2593·2019-08-29 16:44
閱讀 2364·2019-08-29 16:08
閱讀 2409·2019-08-29 16:06
閱讀 1281·2019-08-29 15:21
閱讀 3171·2019-08-29 12:39