记一次面试:
面试官:怎么实现实体的序列化?
答:实现了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(); }