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

Wesley13
• 阅读 744

    提到Java序列化,相信大家都不陌生。我们在序列化的时候,需要将被序列化的类实现Serializable接口,这样的类在序列化时,会默认将所有的字段都序列化。那么当我们在序列化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;
    }
}

    测试类:

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接口有两个方法,分别表示在序列化的时候需要序列化哪些字段和反序列化的时候能够反序列化哪些字段:

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

    于是就有了下面的代码:

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以外,其他保持不变。下面是执行结果:

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。

    需要注意的是:对于恢复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接口搭配使用

点赞
收藏
评论区
推荐文章
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java中的序列化
一、什么是java序列化  序列化:将对象写入IO流反序列化:从IO流中恢复对象序列化机制允许将实现序列化的java对象转换为字节序列,这些字节序列可以保存在磁盘上也可以通过网络传输,字节序列也可以再恢复为原来的对象。序列化机制可以让对象不依附于程序独立存在。二、应用场景
Wesley13 Wesley13
3年前
javaBean为什么要implements Serializable
一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。    什么情况下需要序列化:    1.     当
Wesley13 Wesley13
3年前
FastJson、Jackson、Gson进行Java对象转换Json的细节处理
Java对象转换Json的细节处理前言Java对象在转json的时候,如果对象里面有属性值为null的话,那么在json序列化的时候要不要序列出来呢?对比以下json转换方式一、fastJson1、fastJson在转换java对象为json的时候,默认是不序列化nu
Wesley13 Wesley13
3年前
Java序列化
在java中序列化对象需要实现一个接口,表示该对象可以被序列化java.io.Serializable接下来介绍一个关键字transient这个关键字的意思就是取反:如果一个对象实现了Serializable接口,加上这个关键字表示这个对象不能被序列化;如果一个对象没有实现Serializable接口,加上这个关键字表
红烧土豆泥 红烧土豆泥
3年前
解决Redis序列化Java8的LocalDateTime问题
在从Redis获取带有LocalDateTime类型属性的对象时,产生序列化和反序列化问题解决办法方式一:实体类上指定LocalDateTime的序列化器和反序列化器java@JsonDeserialize(usingLocalDateTimeDeserializer.class)//反序列化@JsonSerialize(usingLo
Stella981 Stella981
3年前
Gson 数据解析
gson和其他现有javajson类库最大的不同时gson需要序列化的实体类不需要使用annotation来标识需要序列化的字段,同时gson又可以通过使用annotation来灵活配置需要序列化的字段。下面是一个简单的例子:1.public class Person {3.private String name;
Easter79 Easter79
3年前
String、StringBuffer、StringBuilder源码解析
String源码分析一:实现接口。publicfinalclassString  implementsjava.io.Serializable,Comparable<String,CharSequence{java.io.Serializable    这个序列化接口没有任何方法和域,仅
Stella981 Stella981
3年前
FastJson 反序列化注意事项
问题描述使用fastJson对json字符串进行反序列化时,有几个点需要注意一下:反序列化内部类反序列化模板类0\.Getter/Setter问题如我们希望返回的一个json串为"name":"name","isDeleted":true,"isEmpty":1
Wesley13 Wesley13
3年前
Java并发编程:Java 序列化的工作机制
JDK内置同步器的实现类经常会看到java.io.Serializable接口,这个接口即是Java序列化操作,这样看来序列化也是同步器的一种机制。 关于序列化本文主要分析Java中的序列化机制,并看看AQS同步器的序列化,掌握序列化机制才能完整理解JDK内置的同步工具的实现。在程序中为了能直接以Java对象的形式进行保存,然后再