Java序列化——transient關鍵字和Externalizable接口

    提到Java序列化,相信你們都不陌生。咱們在序列化的時候,須要將被序列化的類實現Serializable接口,這樣的類在序列化時,會默認將全部的字段都序列化。那麼當咱們在序列化Java對象時,若是不但願對象中某些字段被序列化(如密碼字段),怎麼實現呢?看一個例子:
java

import java.io.Serializable;
import java.util.Date;

public class LoginInfo implements Serializable {
    private static final long serialVersionUID = 8364988832581114038L;
    private String userName;
    private transient String password;//Note this key word "transient"
    private Date loginDate;
    
    //Default Public Constructor
    public LoginInfo() {
        System.out.println("LoginInfo Constructor");
    }
    
    //Non-Default constructor
    public LoginInfo(String username, String password) {
        this.userName = username;
        this.password = password;
        loginDate = new Date();
    }
    public String toString() {
        return "UserName=" + userName + ", Password=" 
                + password + ", LoginDate=" + loginDate;
    }
}

    測試類:
ide

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
    static String fileName = "C:/x.file";
    public static void main(String[] args) throws Exception {
        LoginInfo info = new LoginInfo("name", "123");
        System.out.println(info);
        //Write
        System.out.println("Serialize object");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
        oos.writeObject(info);
        oos.close();
        //Read
        System.out.println("Deserialize object.");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
        LoginInfo info2 = (LoginInfo)ois.readObject();
        ois.close();
        System.out.println(info2);
    }
}

    執行結果:
測試

UserName=name, Password=123, LoginDate=Wed Nov 04 16:41:49 CST 2015
Serialize object
Deserialize object.
UserName=name, Password=null, LoginDate=Wed Nov 04 16:41:49 CST 2015

    另外一種能夠達到此目的的方法可能就比較少用了,那就是——不實現Serializable而實現Externalizable接口。這個Externalizable接口有兩個方法,分別表示在序列化的時候須要序列化哪些字段和反序列化的時候可以反序列化哪些字段
this

void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

    因而就有了下面的代碼:
spa

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;

public class LoginInfo2 implements Externalizable {
    private static final long serialVersionUID = 8364988832581114038L;
    private String userName;
    private String password;
    private Date loginDate;
    
    //Default Public Constructor
    public LoginInfo2() {
        System.out.println("LoginInfo Constructor");
    }
    
    //Non-Default constructor
    public LoginInfo2(String username, String password) {
        this.userName = username;
        this.password = password;
        loginDate = new Date();
    }
    public String toString() {
        return "UserName=" + userName + ", Password=" 
                + password + ", LoginDate=" + loginDate;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Externalizable.writeExternal(ObjectOutput out) is called");
        out.writeObject(loginDate);
        out.writeUTF(userName);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("Externalizable.readExternal(ObjectInput in) is called");
        loginDate = (Date)in.readObject();
        userName = (String)in.readUTF();
    }
}

    測試類除了類名使用LoginInfo2之外,其餘保持不變。下面是執行結果:
rest

UserName=name, Password=123, LoginDate=Wed Nov 04 16:36:39 CST 2015
Serialize object
Externalizable.writeExternal(ObjectOutput out) is called
Deserialize object.
LoginInfo Constructor //-------------------------Note this line
Externalizable.readExternal(ObjectInput in) is called
UserName=name, Password=null, LoginDate=Wed Nov 04 16:36:39 CST 2015

    能夠看到,反序列化後的Password一項依然爲null。
code

    須要注意的是:對於恢復Serializable對象,對象徹底以它存儲的二進制爲基礎來構造,而不調用構造器。而對於一個Externalizable對象,public的無參構造器將會被調用(所以你能夠看到上面的測試結果中有LoginInfoConstructor這一行),以後再調用readExternal()方法。在Externalizable接口文檔中,也給出了相關描述:
對象

When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.接口

    若是沒有發現public的無參構造器,那麼將會報錯。(把LoginInfo2類的無參構造器註釋掉,就會產生錯誤了)文檔

UserName=name, Password=123, LoginDate=Wed Nov 04 17:03:24 CST 2015
Serialize object
Deserialize object.
Exception in thread "main" java.io.InvalidClassException: LoginInfo2; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
	at Test2.main(Test2.java:19)

    那麼,若是把Externalizable接口和transient關鍵字一塊兒用,會是什麼效果呢?咱們在LoginInfo2中的password加上關鍵字transient,再修改writeExternal()和readExternal()方法:

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(loginDate);
        out.writeUTF(userName);
        out.writeUTF(password);//強行將transient修飾的password屬性也序列化進去
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        loginDate = (Date)in.readObject();
        userName = (String)in.readUTF();
        password = (String)in.readUTF();//反序列化password字段
    }

    執行結果:

UserName=name, Password=123, LoginDate=Wed Nov 04 16:58:27 CST 2015
Serialize object
Deserialize object.
LoginInfo Constructor
UserName=name, Password=123, LoginDate=Wed Nov 04 16:58:27 CST 2015

    從結果中能夠看到,儘管在password字段上使用了transient關鍵字,可是這仍是沒能阻止被序列化。由於不是以Serializable方式去序列化和反序列化的。也就是說:transient關鍵字只能與Serializable接口搭配使用

相關文章
相關標籤/搜索