序列化是將對象的狀態(tài)轉(zhuǎn)換為字節(jié)流;反序列化恰恰相反。換言之,序列化是將Java對象轉(zhuǎn)換為字節(jié)的靜態(tài)流(序列,然后可以將其保存到數(shù)據(jù)庫或通過網(wǎng)絡傳輸。
序列化和反序列化
序列化過程是獨立于實例的,即對象可以在一個平臺上序列化并在另一個平臺上反序列化。有資格序列化的類需要實現(xiàn)一個特殊的標記接口Serializable。
ObjectInputStream和ObjectOutputStream都是分別擴展java.io.InputStream和java.io.OutputStream的高級類。 ObjectOutputStream可以將對象的基本類型和對象作為字節(jié)流寫入OutputStream。隨后可以使用ObjectInputStream讀取這些流。
ObjectOutputStream提供了writeObject方法可以將可序列化的對象轉(zhuǎn)換為字節(jié)的序列(流),同樣地,ObjectInputStream提供了readObject方法可以將字節(jié)流轉(zhuǎn)換為Java對象。
請注意,靜態(tài)字段屬于類(與對象相對)并不會被序列化;另外,也可以使用關鍵字transient忽略字段序列化。
用一個Person類來說明序列化,其源碼如下:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
static String country = "ITALY";
private int age;
private String name;
transient int height;
// getters and setters
}
@Test
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() ()
throws IOException, ClassNotFoundException {
Person person = new Person();
person.setAge(20);
person.setName("Joe");
FileOutputStream fileOutputStream
= new FileOutputStream("yourfile.txt");
ObjectOutputStream objectOutputStream
= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
objectOutputStream.flush();
objectOutputStream.close();
FileInputStream fileInputStream
= new FileInputStream("yourfile.txt");
ObjectInputStream objectInputStream
= new ObjectInputStream(fileInputStream);
Person p2 = (Person) objectInputStream.readObject();
objectInputStream.close();
assertTrue(p2.getAge() == p.getAge());
assertTrue(p2.getName().equals(p.getName()));
}
序列化注意事項
繼承與構成
當一個類實現(xiàn)了java.io.Serializable接口時,它的所有子類也是可序列化的。相反,當一個對象具有對另一個對象的引用時,這些對象必須單獨實現(xiàn)Serializable接口,否則會引發(fā)NotSerializableException異常。
public class Person implements Serializable {
private int age;
private String name;
private Address country; // must be serializable too
}
Serial Version UID
JVM將版本號與每個可序列化的類相關聯(lián)。它用于驗證保存和加載的對象具有相同的屬性,因此在序列化時兼容。
如果可序列化的類沒有聲明serialVersionUID,則JVM將在運行時自動生成一個。但是,強烈建議每個類聲明其serialVersionUID,因為生成的是依賴于編譯器的,因此可能會導致意外的InvalidClassExceptions。
Java中的自定義序列化
Java指定了可以序列化對象的默認方式。Java類可以覆蓋此默認行為。在嘗試序列化具有一些不可序列化屬性的對象時,自定義序列化特別有用。
1、在類中提供writeObject和readObject方法
可以通過在類中提供兩個我們想要序列化的方法來完成:
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws
IOException, ClassNotFoundException;
通過這些方法,我們可以將那些不可序列化的屬性序列化為可以序列化的其他形式:
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private transient Address address;
private Person person;
// setters and getters
private void writeObject(ObjectOutputStream oos)
throws IOException {
oos.defaultWriteObject();
oos.writeObject(address.getHouseNumber());
}
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
ois.defaultReadObject();
Integer houseNumber = (Integer) ois.readObject();
Address a = new Address();
a.setHouseNumber(houseNumber);
this.setAddress(a);
}
}
public class Address {
private int houseNumber;
// setters and getters
}
測試自定義序列化:
@Test
public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame()
throws IOException, ClassNotFoundException {
Person p = new Person();
p.setAge(20);
p.setName("Joe");
Address a = new Address();
a.setHouseNumber(1);
Employee e = new Employee();
e.setPerson(p);
e.setAddress(a);
FileOutputStream fileOutputStream
= new FileOutputStream("yourfile2.txt");
ObjectOutputStream objectOutputStream
= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(e);
objectOutputStream.flush();
objectOutputStream.close();
FileInputStream fileInputStream
= new FileInputStream("yourfile2.txt");
ObjectInputStream objectInputStream
= new ObjectInputStream(fileInputStream);
Employee e2 = (Employee) objectInputStream.readObject();
objectInputStream.close();
assertTrue(
e2.getPerson().getAge() == e.getPerson().getAge());
assertTrue(
e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber());
}
2、實現(xiàn)Externalizable接口
除了Serializable 之外,java中還提供了另一個序列化接口Externalizable。
Externalizable繼承了Serializable,該接口中定義了兩個抽象方法:writeExternal()與readExternal()。當使用Externalizable接口來進行序列化與反序列化的時候需要開發(fā)人員重寫writeExternal()與readExternal()方法。
public class Person implements Externalizable {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getCountry() {
return country;
}
public void setCountry(Address country) {
this.country = country;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(age);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
age = in.readInt();
name = (String) in.readObject();
}
}
測試代碼如下:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("yourfile2.txt"));
Person person = new Person();
person.setAge(10);
person.setName("zhang san");
oos.writeObject(person);
//Read Obj from file
File file = new File("yourfile2.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Person newInstance = (Person) ois.readObject();
值得注意:在使用Externalizable進行序列化的時候,在讀取對象時,會調(diào)用被序列化類的無參構造器去創(chuàng)建一個新的對象,然后再將被保存對象的字段的值分別填充到新對象中。所以,實現(xiàn)Externalizable接口的類必須要提供一個public的無參的構造器。