Java序列化——transient关键字和Externalizable接口

    提到Java序列化,相信大家都不陌生。我们在序列化的时候,需要将被序列化的类实现Serializable接口,这样的类在序列化时,会默认将所有的字段都序列化。那么当我们在序列化Java对象时,如果不希望对象中某些字段被序列化(如密码字段),怎么实现呢?看一个例子:

1import java.io.Serializable; 2import java.util.Date; 3 4public class LoginInfo implements Serializable { 5    private static final long serialVersionUID = 8364988832581114038L; 6    private String userName; 7    private transient String password;//Note this key word "transient" 8    private Date loginDate; 9     10    //Default Public Constructor 11    public LoginInfo() { 12        System.out.println("LoginInfo Constructor"); 13    } 14     15    //Non-Default constructor 16    public LoginInfo(String username, String password) { 17        this.userName = username; 18        this.password = password; 19        loginDate = new Date(); 20    } 21    public String toString() { 22        return "UserName=" + userName + ", Password="  23                + password + ", LoginDate=" + loginDate; 24    } 25}

    测试类:

1import java.io.FileInputStream; 2import java.io.FileOutputStream; 3import java.io.ObjectInputStream; 4import java.io.ObjectOutputStream; 5 6public class Test { 7    static String fileName = "C:/x.file"; 8    public static void main(String[] args) throws Exception { 9        LoginInfo info = new LoginInfo("name", "123"); 10        System.out.println(info); 11        //Write 12        System.out.println("Serialize object"); 13        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName)); 14        oos.writeObject(info); 15        oos.close(); 16        //Read 17        System.out.println("Deserialize object."); 18        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName)); 19        LoginInfo info2 = (LoginInfo)ois.readObject(); 20        ois.close(); 21        System.out.println(info2); 22    } 23}

    执行结果:

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

    另一种可以达到此目的的方法可能就比较少用了,那就是——不实现Serializable而实现Externalizable接口。这个Externalizable接口有两个方法,分别表示在序列化的时候需要序列化哪些字段和反序列化的时候能够反序列化哪些字段:

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

    于是就有了下面的代码:

1import java.io.Externalizable; 2import java.io.IOException; 3import java.io.ObjectInput; 4import java.io.ObjectOutput; 5import java.util.Date; 6 7public class LoginInfo2 implements Externalizable { 8    private static final long serialVersionUID = 8364988832581114038L; 9    private String userName; 10    private String password; 11    private Date loginDate; 12     13    //Default Public Constructor 14    public LoginInfo2() { 15        System.out.println("LoginInfo Constructor"); 16    } 17     18    //Non-Default constructor 19    public LoginInfo2(String username, String password) { 20        this.userName = username; 21        this.password = password; 22        loginDate = new Date(); 23    } 24    public String toString() { 25        return "UserName=" + userName + ", Password="  26                + password + ", LoginDate=" + loginDate; 27    } 28 29    @Override 30    public void writeExternal(ObjectOutput out) throws IOException { 31        System.out.println("Externalizable.writeExternal(ObjectOutput out) is called"); 32        out.writeObject(loginDate); 33        out.writeUTF(userName); 34    } 35 36    @Override 37    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 38        System.out.println("Externalizable.readExternal(ObjectInput in) is called"); 39        loginDate = (Date)in.readObject(); 40        userName = (String)in.readUTF(); 41    } 42}

    测试类除了类名使用LoginInfo2以外,其他保持不变。下面是执行结果:

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

    可以看到,反序列化后的Password一项依然为null。

    需要注意的是:对于恢复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类的无参构造器注释掉,就会产生错误了):

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

    那么,如果把Externalizable接口和transient关键字一起用,会是什么效果呢?我们在LoginInfo2中的password加上关键字transient,再修改writeExternal()和readExternal()方法:

1    @Override 2    public void writeExternal(ObjectOutput out) throws IOException { 3        out.writeObject(loginDate); 4        out.writeUTF(userName); 5        out.writeUTF(password);//强行将transient修饰的password属性也序列化进去 6    } 7 8    @Override 9    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 10        loginDate = (Date)in.readObject(); 11        userName = (String)in.readUTF(); 12        password = (String)in.readUTF();//反序列化password字段 13    }

    执行结果:

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

    从结果中可以看到,尽管在password字段上使用了transient关键字,但是这还是没能阻止被序列化。因为不是以Serializable方式去序列化和反序列化的。也就是说:transient关键字只能与Serializable接口搭配使用。

点赞
收藏

评论区

加载中...

相关推荐

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java中的序列化

一、什么是java序列化  序列化:将对象写入IO流反序列化:从IO流中恢复对象序列化机制允许将实现序列化的java对象转换为字节序列,这些字节序列可以保存在磁盘上也可以通过网络传输,字节序列也可以再恢复为原来的对象。序列化机制可以让对象不依附于程序独立存在。二、应用场景

javaBean为什么要implements Serializable

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。    什么情况下需要序列化:    1.     当

FastJson、Jackson、Gson进行Java对象转换Json的细节处理

Java对象转换Json的细节处理前言Java对象在转json的时候,如果对象里面有属性值为null的话,那么在json序列化的时候要不要序列出来呢?对比以下json转换方式一、fastJson1、fastJson在转换java对象为json的时候,默认是不序列化nu

Java序列化

在java中序列化对象需要实现一个接口,表示该对象可以被序列化java.io.Serializable接下来介绍一个关键字transient这个关键字的意思就是取反:如果一个对象实现了Serializable接口,加上这个关键字表示这个对象不能被序列化;如果一个对象没有实现Serializable接口,加上这个关键字表

解决Redis序列化Java8的LocalDateTime问题

在从Redis获取带有LocalDateTime类型属性的对象时,产生序列化和反序列化问题解决办法方式一:实体类上指定LocalDateTime的序列化器和反序列化器java@JsonDeserialize(usingLocalDateTimeDeserializer.class)//反序列化@JsonSerialize(usingLo

Java序列化——transient关键字和Externalizable接口 - HelloWorld