#在介紹序列化之前,首先要知道以下幾個概念:
非持久化:對於存在JVM(Java 虛擬機)的對象,其內部的狀態只能保持在記憶體中,一旦JVM 停止工作,內部的狀態也就消失了,所以它是非持久化的。
持久化:如果想要永久的保存物件(即持久化),通常的作法是將其儲存到檔案或資料庫。
序列化:在Java 中想要實現物件的持久化,就需要將其序列化,透過序列化,可以很容易的將JVM 中的活動物件轉換成位元組數組(流)進行儲存。
反序列化:將檔案或資料庫中的位元組陣列(流)轉換成 JVM的活動物件。
在 Java 中,類別可以透過實作 Serializable、Externalizable 介面來序列化。
類別一旦實作了 Serializable 接口,表示它可以被序列化。
特定的序列化/反序列化操作則需要透過物件流(ObjectOutputStream /ObjectInputStream )實現。
下面來看具體的例子:
class Person implements Serializable { private String name; private int age; public Person(String name ,int age){ this.name = name; this.age =age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "name is " + name + " , age is " + age; } }public class Test { private final static String TEMPFILE = "E:" + File.separator + "test.txt"; public static void main(String[] args) { Person person = new Person("test",100); write(person); // 关键 -> 序列化之后重新对对象进行赋值 person = new Person("hello",999); read(person); // 输出结果: // name is test , age is 100,序列化成功 // 反序列化成功,name is test , age is 100 } // 通过 ObjectOutputStream 进行对象的序列化操作 private static void write(Person person) { try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(TEMPFILE)); oos.writeObject(person); oos.close(); System.out.println(person+",序列化成功"); } catch (Exception e) { e.printStackTrace(); } } // 通过 ObjectInputStream 进行对象的反序列化操作 private static void read(Person person) { try { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(TEMPFILE)); person = (Person) ois.readObject(); ois.close(); System.out.println("反序列化成功.,"+person); } catch (Exception e) { e.printStackTrace(); } } }
觀察輸出結果,我們在物件序列化之後重新對其賦值,而透過反序列化的結果依然與物件序列化之前的值一致,這也間接證明物件被永久的儲存下來,實現了持久化。
序列化失敗,有兩種情況:不想被序列化、無法被序列化。
在類別中,被關鍵字 transient、static 修飾的成員變量,表示自己不想被序列化。它會導致局部序列化失敗。
在類別中,存在成員變數是 Thead 類型,則該類別無法被序列化,它的影響是整體的。
下面來看這幾種情況:
但凡變數被其修飾時,表示該參數是瞬態的,不想被序列化(不是無法序列化)。
// 序列化过程、调用过程与上述例子一致,省略代码... // 这里只对内部类 Person 的 age 属性进行修改class Person implements Serializable { // 用 transient 修该变量 private transient int age; // 省略部分代码...} // 调用后的输出结果: // name is test , age is 100,序列化成功 // 反序列化成功.,name is test , age is 0
觀察輸出結果,發現序列化之前 age 的是 100,而透過反序列化讀取的 age 為 0。
int 類型的參數初始值為 0,這也正說明了該參數沒有被序列化。
但凡被其修飾的變數代表全域變量,在存取全域變數時可以不依賴類別物件。而序列化的操作是為了保存物件。正是由於此特性與序列化的矛盾,導致全域變數預設不被序列化(不是無法序列化)。
// 序列化过程、调用过程与上述例子一致,省略代码... // 这里只对内部类 Person 的 name 属性进行修改class Person implements Serializable { // 用 static 修该变量 private static String name; // 省略部分代码...} // 输出结果: // name is test , age is 100,序列化成功//反序列化成功.,name is hello , age is 100
觀察輸出結果,發現 name 的值為 hello,不是序列化之前的 test。
前後值不一樣說明其沒有被序列化。又因為 static 的特性是與類別有關,該變數序列化操作之後被重新賦值了,導致其值不是 null,而是 hello。
如果物件的成員變數含有 Thread 類型,是無法被序列化的。
// 序列化过程、调用过程与上述例子一致,省略代码... // 这里只对内部类 Person 的 name 属性进行修改class Person implements Serializable { //新增成员变量 private Thread myThread = new Thread(); //省略部分代码...} // 输出结果(抛出异常): // Caused by: java.io.NotSerializableException: java.lang.Thread
上面介紹了序列化失敗的情況,那如果某些情況非要實現序列化呢(例如一定要實現全域變數的序列化)。那麼就需要在類別中自訂序列化/反序列化過程,來看下面這個範例:
// 序列化操作代码与上面一致... // 这里只对内部类 Person 的属性进行修改。class Person implements Serializable { private static String name; private transient int age; // 自定义序列化操作 private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject(); out.writeObject(name); out.writeInt(age); } // 自定义反序列化操作 private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ in.defaultReadObject(); name = (String) in.readObject(); age = in.readInt(); } // 省略部分代码...} // 输出结果: // name is test , age is 100,序列化成功 // 反序列化成功,name is test , age is 100
writeExternal 和readExternal 方法用於自訂序列化與反序列化過程。
無參考建構子。
// 序列化操作代码与上面一致,这里只对内部类 Person 的进行修改。 class Person implements Externalizable { private static String name; private transient int age; // 重点 ->必须有无参构造函数 public Person(){ } public Person(String name ,int age){ this.name = name; this.age =age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "name is " + name + " , age is " + age; } // 实现接口的方法 @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeInt(age); } // 实现接口的方法 @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); age = in.readInt(); } } // 输出结果: // name is test , age is 100,序列化成功 // 反序列化成功,name is test , age is 100
方法進行修改時,serialVersionUID**不變**。
屬性進行修改時,重新產生的serialVersionUID**會改變**。
因此说明序列化是作用于对象属性上的。
下面通过实例来探究下 serialVersionUID 的具体作用:
首先我们对对象进行序列化操作(这里取消了反序列化的操作)
// 序列化操作代码与上面例子一致...// 这里取消了反序列化的操作public static void main(String[] args) { Person person = new Person("test",100); write(person); person = new Person("java", 200); //read(person);}
然后新增一个对象的属性,再进行反序列化操作
// 省略部分代码,与上面的代码一致...class Person implements Serializable { private String name; private age; //新增成员变量 private int phone; //省略部分代码... } }public static void main(String[] args) { Person person = new Person("test",100); //write(person); person = new Person("java", 200); read(person); }// 输出结果(抛出异常):// java.io.InvalidClassException: Person;
观察代码,在序列化对象并没有添加 serialVersionUID 的情况,在对象序列化之后如果改变了对象的属性,反序列化就会抛出异常。如果对象添加了 serialVersionUID 就不会出现这种情况,这里就不验证了。
以上就是13.Java 基础 - 序列化的内容,更多相关内容请关注PHP中文网(www.php.cn)!