Java序列化:Serializable 和 Externalizable 接口

记一次面试:

面试官:怎么实现实体的序列化?

答:实现了Serializable接口

面试官:哦,确定吗?还有其他答案吗?

答:确定,暂时没有

面试官:那怎么避免某个字段参与序列化?

答:使用transient关键字。

面试官:哦,确定吗?还有其他答案吗?

答:确定啊

面试挂,给我反馈是基础知识不扎实。。。。

 

测试类:

Player 和 Weapon 都实现了Serializable接口(成员变量引用的类也必须实现了Serializable接口)

public class Player implements Serializable {

    private static final long serialVersionUID = -75L;

    private String name;
    private int level;
    private Weapon weapon;

    public Player(){
        name = "sam";
        level = 10;
        weapon = new Weapon();
    }
    
    @Override
    public String toString() {
        return "Player{" +
                "name='" + name + '\'' +
                ", level=" + level +
                ", weapon=" + weapon.getName() +
                '}';
    }
}
public class Weapon implements Serializable {

    private static final long serialVersionUID = -89812L;

    private String name;

    public Weapon(String name) {
        this.name = name;
    }

    public Weapon() {
        this.name = "AK74";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

使用ObjectOutputStream 和 ObjectInputStream 实现序列化和反序列化

        File of = new File("/Users/sam/ooof");

        /**
         *  Serializable 开始
         */

        Player r1 = new Player();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(of));

        oos.writeObject(r1);

        oos.close();
        

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(of));

        Player afterRead = (Player) ois.readObject();

        ois.close();

        System.out.println(afterRead);

        System.out.println("------------------------------------------------------");

可以看到r1被写入文件,再次读取出来之后,成员变量的值仍保留。

使用transient关键字来阻止字段被序列化:

再次执行测试例程,可见name字段是空的:

 

实现Externalizable接口(非自动序列化):

PlayerEx类,实现Externalizable接口中的writeExternal和readExternal方法,其中Weapon类不是必须实现Externalizable接口了,只要实现Serializable接口即可:

package com.sam.uvb76.book.serializable;

import java.io.*;

public class PlayerEx implements Externalizable {

    private static final long serialVersionUID = -75L;

    private transient String name;
    private int level;
    private Weapon weapon;

    public PlayerEx(){
        name = "sam";
        level = 10;
        weapon = new Weapon();
    }

    @Override
    public String toString() {
        return "Player{" +
                "name='" + name + '\'' +
                ", level=" + level +
                ", weapon=" + weapon.getName() +
                '}';
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(level);
        out.writeObject(weapon);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        level = (int) in.readObject();
        weapon = (Weapon) in.readObject();
    }
}

再次执行例程:

可见,name字段虽然有 transient关键字约束,但仍会被序列化和逆序列化。

那怎么让name字段不参与序列化:writeExternal中将对应字段写入空值,或readExternal读出后自己设置为空值即可。

经过试验,如果writeExternal和readExternal不对某个字段处理,该字段还是会成功参与序列化的。

transient关键字只能和Serializable接口搭配使用,Externalizable接口默认所有字段参与序列化

 

注意:实现Externalizable接口的实体类需有public无参构造方法

即:要么自己写一个形参为空构造方法,要么不写,编译器会自动生成一个空的构造方法。但实体类中不可只写一个有参构造方法,这样编译器不会帮我们生成那个无参的构造方法了。例如:

执行测试例程执行会报异常:

 

实现Externalizable接口,优势在于通过writeExternal和readExternal方法,可以在序列化或逆序列化过程中对成员字段加入自己的处理逻辑,更加灵活:

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(level + 100);
        out.writeObject(weapon);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name =  in.readObject() + "Externalizable接口实现的";
        level = (int) in.readObject();
        weapon = (Weapon) in.readObject();
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章