Java基礎知識系列—序列化

序列化是將對象的狀態(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的無參的構造器。

參考資料

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 11,221評論 0 24
  • 原帖地址:原帖個人網(wǎng)站地址:個人網(wǎng)站簡書對markdown的支持太完美了,我竟然可以直接Ctrl C/V過來。 定...
    ryderchan閱讀 3,949評論 1 9
  • Java性能問題一直困擾著廣大程序員,由于平臺復雜性,要定位問題,找出其根源確實很難。隨著10多年Java平臺的改...
    程序員技術圈閱讀 4,964評論 0 65
  • 開了將近一小時的車程,在穿過春天城市的陽光與連篇花枝招展的三角梅,我與“前男友”老 A,在一個城效...
    麥田下閱讀 248評論 0 1
  • 過完年上班,跟銷售妹紙聊天,話題是如何在新的一年快速成長。妹紙無奈地說道:“Lewis,我其實每年都有給自己設立目...
    Lewis08閱讀 661評論 0 7

友情鏈接更多精彩內(nèi)容