數年前,當和一個軟件團隊一起用 Java 語言編寫一個應用程序時,我體會到比一般程序員多知道一點關於 Java 對象序列化的知識所帶來的好處。
大約一年前,一個負責管理應用程序所有用戶設置的開發人員,決定將用戶設置存儲在一個 Hashtable 中,然後將這個 Hashtable 序列化到磁盤,以便持久化。當用戶更改設置時,便重新將 Hashtable 寫到磁盤。
這是一個優雅的、開放式的設置系統,但是,當團隊決定從 Hashtable 遷移到 Java Collections 庫中的 HashMap 時,這個系統便面臨崩潰。
Hashtable 和 HashMap 在磁盤上的格式是不相同、不兼容的。除非對每個持久化的用戶設置運行某種類型的數據轉換實用程序(極其龐大的任務),否則以後似乎只能一直用 Hashtable 作為應用程序的存儲格式。
團隊感到陷入僵局,但這只是因為他們不知道關於 Java 序列化的一個重要事實:Java 序列化允許隨著時間的推移而改變類型。當我向他們展示如何自動進行序列化替換後,他們終於按計劃完成了向 HashMap 的轉變。
本文是本系列的第一篇文章,這個系列專門揭示關於 Java 平台的一些有用的小知識 — 這些小知識不易理解,但對於解決 Java 編程挑戰遲早有用。
將 Java 對象序列化 API 作為開端是一個不錯的選擇,因為它從一開始就存在於 JDK 1.1 中。本文介紹的關於序列化的 5 件事情將說服您重新審視那些標准 Java API。
Java 序列化簡介
Java 對象序列化是 JDK 1.1 中引入的一組開創性特性之一,用於作為一種將 Java 對象的狀態轉換為字節數組,以便存儲或傳輸的機制,以後,仍可以將字節數組轉換回 Java 對象原有的狀態。
實際上,序列化的思想是 “凍結” 對象狀態,傳輸對象狀態(寫到磁盤、通過網絡傳輸等等),然後 “解凍” 狀態,重新獲得可用的 Java 對象。所有這些事情的發生有點像是魔術,這要歸功於 ObjectInputStream/ObjectOutputStream 類、完全保真的元數據以及程序員願意用 Serializable 標識接口標記他們的類,從而 “參與” 這個過程。
清單 1 顯示一個實現 Serializable 的 Person 類。
清單 1. Serializable Person
- package com.tedneward;
- public class Person
- implements Java.io.Serializable
- {
- public Person(String fn, String ln, int a)
- {
- this.firstName = fn; this.lastName = ln; this.age = a;
- }
- public String getFirstName() { return firstName; }
- public String getLastName() { return lastName; }
- public int getAge() { return age; }
- public Person getSpouse() { return spouse; }
- public void setFirstName(String value) { firstName = value; }
- public void setLastName(String value) { lastName = value; }
- public void setAge(int value) { age = value; }
- public void setSpouse(Person value) { spouse = value; }
- public String toString()
- {
- return "[Person: firstName=" + firstName +
- " lastName=" + lastName +
- " age=" + age +
- " spouse=" + spouse.getFirstName() +
- "]";
- }
- private String firstName;
- private String lastName;
- private int age;
- private Person spouse;
- }
將 Person 序列化後,很容易將對象狀態寫到磁盤,然後重新讀出它,下面的 JUnit 4 單元測試對此做了演示。
清單 2. 對 Person 進行反序列化
- public class SerTest
- {
- @Test public void serializeToDisk()
- {
- try
- {
- com.tedneward.Person ted = new com.tedneward.Person("Ted", "Neward", 39);
- com.tedneward.Person charl = new com.tedneward.Person("Charlotte",
- "Neward", 38);
- ted.setSpouse(charl); charl.setSpouse(ted);
- FileOutputStream fos = new FileOutputStream("tempdata.ser");
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- oos.writeObject(ted);
- oos.close();
- }
- catch (Exception ex)
- {
- fail("Exception thrown during test: " + ex.toString());
- }
- try
- {
- FileInputStream fis = new FileInputStream("tempdata.ser");
- ObjectInputStream ois = new ObjectInputStream(fis);
- com.tedneward.Person ted = (com.tedneward.Person) ois.readObject();
- ois.close();
- assertEquals(ted.getFirstName(), "Ted");
- assertEquals(ted.getSpouse().getFirstName(), "Charlotte");
- // Clean up the file
- new File("tempdata.ser").delete();
- }
- catch (Exception ex)
- {
- fail("Exception thrown during test: " + ex.toString());
- }
- }
- }