Java序列化的機制原理(二)

# 引言
在上文中我們講解了在java中怎麼序列化一個類,並且分析了序列化後的數據。已經對序列化有了一個初步認識,在今天我們在嘗試另外一種序列化方式。
# 正文
## 場景演示
現在假設一個場景,一個User實體需要序列化,並且不需要屬性age序列化。首先根據之前所學,先實現Serializable,但是我們知道,實現Serializable後所有屬性都會序列化,怎樣避免age不被序列化,那就要用到**transient**,次關鍵詞的意思就是在jvm序列化中,不序列化次關鍵詞修飾的屬性,但是在jvm還是會給一個默認值,基本數據類型中int,long之類的默認爲0,boolean默認爲false,引用類型默認爲null。
``` 
public class User implements Serializable {
    private String id;
    private String username;
    private String pwd;
    private transient Integer age;
    private Integer gender;
    ......
//省略其他屬性和setget方法
}
```
現在又有一個新的問題出現了,pwd要進行加密處理傳輸(假設pwd是明文),只傳遞pwd的密文和usernam這兩個屬性,我們知道在序列化後所有的值都是可以通過數值轉換得到,所以直接實現Serializable是不可能滿足需求。那麼有法便有破,在java中有一個接口繼承了Serializable接口,並新增兩個方法。爲用戶提供自定義序列化方式,那就是**Externalizable**,用於滿足用戶自定義序列化數據。
## Externalizable強制自定義序列化
``` 
public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}  
```
Externalizable繼承於Serializable接口,內部有兩個方法```void writeExternal(ObjectOutput out) throws IOException;```和```void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;```從名字就可看出這兩個方法跟序列化和反序列化有關。
在序列化過程中,JVM會試圖調用對象類裏的writeObject和readObject方法,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認調用是ObjectOutputStream的defaultWriteObject方法以及 ObjectInputStream的defaultReadObject方法。如果用戶重寫的writeObject和readObject方法,jvm就以用戶控制序列化的方式來序列化數據。
那麼上文的類就可以改造成User實現Externalizable,並且重寫writeExternal和readExternal方法,在writeExternal自定義序列化數據。
```
public class User implements Externalizable {
    private String id;
    private String username;
    private String pwd;
    private Integer age;
    private Integer gender;
    //注意,必須加上public無參構造器
    public User() {
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //假裝加密
        pwd="假裝加密";
        out.writeObject(pwd);
        out.writeObject(username);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //假裝解密
        pwd= in.readObject().toString();
        username= in.readObject().toString();
    }
}
    public static void main(String[] args) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User.txt"));
        User obj = new User();
        obj.setUsername("mm");
        obj.setPwd("12345678");
        oos.writeObject(obj);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User.txt"));
        User user = (User) ois.readObject();
}
```
在代碼中,特地強調了要聲明一個無參的構造方法。因爲JVM在反序列化的時候會通過反射創建對象。還要有注意的一點就是,在readExternal的方法中取具體的屬值得時候要和writeExternal寫數據的順序要一致,由此可以推斷出屬性值不能多取。經試驗發現如果多取屬性值就會報OptionalDataException異常。原因是讀到了序列化對象的結束,所以系統報錯。
![具體源碼](https://upload-images.jianshu.io/upload_images/8368884-20be3741dabc26a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 總結
在序列化中使用Serializable較爲簡單,只需要實現其方法就可以實現序列化。但是適用範圍較窄、開發人員無法對數據進行操作、性能較差。而實現Externalizable進行序列化較爲複雜,**必須手動設置**(不設置序列化後將是一個空的對象)哪些屬性需要序列化,並需要至少一個無參構造函數。但是開發人員可以對具體屬性進行自定義操作,在數據傳遞過程中可以更好的利用資源和保證數據的安全性。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章