Java 序列化之 Externalizable

相关文章: Java 序列化 之 Serializable

JDK中除了提供 Serializable 序列化接口外,还提供了另一个序列化接口Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。Externalizable 的序列化机制优先级要高于 Serializable 。

Externalizable 源码分析

从源码中,我们可以看到 Externalizable 接口继承了 Serializable 接口。并定义了两个方法 writeExternal 和 readExternal 方法

Externalizable 示例一

User 类

public class User implements Externalizable {

    private static final long serialVersionUID = 1318824539146791009L;
    private String userName;
    private transient String password;

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User [userName=" + userName + ", password=" + password + "]";
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    }
}

User 类有两个属性,一个是非 transient 字段的userName 和一个 transient 字段的password,并实现了 Externalizable 接口,writeExternal 和 readExternal 方法都不填写任何逻辑。 然后下面的代码,来看下序列化和反序列化 User 对象的效果。

public class Test{
    public static void main(String[] args) throws Exception {
        File file = new File("d:\\a.user");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        User user1 = new User();
        user1.setUserName("zhangsan");
        user1.setPassword("123456");
        oos.writeObject(user1);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        User user2 = (User) ois.readObject();
        System.out.println(user2);
    }
}

执行结果: User [userName=null, password=null]

示例二

实现 Externalizable 接口的两个方法的实现,代码如下:

public class User implements Externalizable {

    private static final long serialVersionUID = 1318824539146791009L;
    private String userName;
    private transient String password;

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User [userName=" + userName + ", password=" + password + "]";
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.userName);
        out.writeObject(this.password);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.userName = in.readObject().toString();
        this.password = in.readObject().toString();
    }
}

在 writeExternal 方法中写入 userName 和 password 两个数据,在 readExternal 方法中读取反序列化的信息并赋值给 userName 和 password 两个字段。

执行 Test.main 方法。

执行结果如下: User [userName=zhangsan, password=123456]

结论一

  • 实现 Externalizable 接口后,序列化的细节需要由开发人员自己实现。由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。
  • 实现 Externalizable 接口后,属性字段使用 transient 和不使用没有任何区别。

示例三

public class User implements Serializable, Externalizable {

    private static final long serialVersionUID = 1318824539146791009L;
    private String userName;
    private transient String password;

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User [userName=" + userName + ", password=" + password + "]";
    }
    private void readObject(ObjectInputStream s) throws Exception {
        s.defaultReadObject();
        this.password = (String) s.readObject();
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject("serializable:"+this.password);
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.userName);
        out.writeObject("externalizable:"+this.password);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.userName = (String)in.readObject();
        this.password = (String)in.readObject();
    }
}

User 类既实现了 Serializable 接口,又实现了 Externalizable 接口(其实没啥意义,因为 Externalizable 已经继承了 Serializable 接口)。 在 readObject 和 writeObject 方法中实现了序列化和发序列化。 在 readExternal 和 writeExternal 方法中也实现了序列化和发序列化。

当执行 Test.main() 方法,执行的结果如下:

User [userName=zhangsan, password=externalizable:123456] 可以看出,执行的是 Externalizable ,而不是 Serializable 。

结论二

Externalizable 序列化的优先级比Serializable的优先级高。

示例四

public class User implements Externalizable {

    private static final long serialVersionUID = 1318824539146791009L;
    private String userName;
    private transient String password;
    
    public User(String userName, String password) {
        super();
        this.userName = userName;
        this.password = password;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User [userName=" + userName + ", password=" + password + "]";
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.userName);
        out.writeObject("externalizable:"+this.password);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.userName = (String)in.readObject();
        this.password = (String)in.readObject();
    }
}

User 类实现了 Externalizable 接口,并且有一个带参数的构造方法。

public class Test{
    public static void main(String[] args) throws Exception {
        File file = new File("d:\\a.user");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        User user1 = new User("zhangsan", "123456");
        oos.writeObject(user1);

        System.out.println("-------序列化成功");
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        User user2 = (User) ois.readObject();
        System.out.println(user2);
        System.out.println("-------反序列化成功");
    }
}

执行 Test.main() 方法,执行结果:

-------序列化成功 Exception in thread "main" java.io.InvalidClassException: cn.com.infcn.serial.external.User; no valid constructor

发现报错了,序列化成功了,而没有发序列化成功。提示没有有效的构造方法。 这是因为使用 Externalizable 进行反序列化时,需要有默认的构造方法,通过反射先创建出该类的实例,然后再把解析后的属性值,通过反射赋值。

结论

使用 Externalizable 进行序列化时,必须要有默认的构造方法,而Serializable可以没有默认的构造方法。


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AhDung

【C#】递归搜索指定目录下的指定项目(文件或目录)

---------------更新:201411201121---------------

1382
来自专栏GIS讲堂

数据库 连接(JOIN)

连接运算中有两种最为重要的连接,一种是等值连接(Equijoin),另一种是自然连接(Nature Join):等值连接是从关系R和S中的笛卡尔积中选取A,B属...

2213
来自专栏岑玉海

Spark源码系列(二)RDD详解

1、什么是RDD? 上一章讲了Spark提交作业的过程,这一章我们要讲RDD。简单的讲,RDD就是Spark的input,知道input是啥吧,就是输入的数据。...

3253
来自专栏博客园

设计模式学习-单例模式

但是这么做不感觉有问题吗?假如这个类我们并不使用或在程序启动很久以后我们才使用,那么这个对象的预创建不就很浪费吗?并且如果这个对象的创建需要很大的资源,那......

783
来自专栏JAVA后端开发

给mybatis添加自动建表,自动加字段的功能

以前项目用惯了hibernate,jpa,它有个自动建表功能,只要在PO里加上配置就可以了,感觉很爽. 但现在用mybatis,发现没有该功能,每次都加个字段...

5193
来自专栏程序员的SOD蜜

使用操作符重载,生成ORM实体类的SQL条件语句

ORM框架的一个不可或缺的功能就是根据实体类,生成操作数据库的SQL语句,这其中,最难处理的就是那些复杂的SQL条件比较语句。比如,有下面这样一个SQL语句: ...

23310
来自专栏IMWeb前端团队

Redux源码解析系列(四)-- combineReducers

本文作者:IMWeb 黄qiong 原文出处:IMWeb社区 未经同意,禁止转载 combindeReducer 字面意思就是用来合并reducer的...

2007
来自专栏玩转JavaEE

MongoDB文档查询操作(一)

上篇文章我们主要介绍了MongoDB的修改操作,本文我们来看看查询操作。 本文是MongoDB系列的第五篇文章,了解前面的文章有助于更好的理解本文: ---- ...

3686
来自专栏JavaEdge

为什么java中用枚举实现单例模式会更好代码简洁

代码简洁 这是迄今为止最大的优点,如果你曾经在Java5之前写过单例模式代码,那么你会知道即使是使用双检锁你有时候也会返回不止一个实例对象。虽然这种问题通过...

5774
来自专栏菩提树下的杨过

AS3中的单件(Singleton)模式

单件(singleton)模式在c#中是最容易实现的模式,其主要用意就在于限制使用者用new来创建多个实例。但在as3中,构造函数必须是public的(语法本身...

2195

扫码关注云+社区

领取腾讯云代金券