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接口搭配使用

相关文章
相关标签/搜索