更多 Java 高级知识方面的文章,请参见文集《Java 高级知识》
Java 序列化
实现对象 Object 与字节 byte 的转换。例如在分布式环境中传递对象。
Java 序列化的实现
实现 Serializable
接口,该接口并不包含方法,更像是一个标记。
- 通过
ObjectOutputStream
中的writeObject()
方法将对象转换为字节序列。
实际上操作的是一个对象图,包括该对象所引用的其他对象。 - 通过
ObjectInputStream
中的readObject()
方法将字节序列转换为对象。
遍历对象图并逐个序列化。
通过readObject()
方法来创建对象时,不会调用其构造方法。
示例:
public class Serializable_Test {
public static void main(String[] args) throws Exception {
MyObj obj = new MyObj("ABC");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myobj.txt"));
oos.writeObject(obj);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myobj.txt"));
MyObj obj2 = (MyObj) ois.readObject();
ois.close();
System.out.println(obj2.getName());
}
}
class MyObj implements Serializable {
private String name;
public MyObj(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
以上的代码中,如果 MyObj
没有实现 Serializable
接口,会抛出异常 NotSerializableException
。
如果希望自定义对象序列化的实现方式:
- 继承
ObjectOutputStream
类,重写writeObject()
方法 - 继承
ObjectInputStream
类,重写readObject()
方法
哪些字段会被序列化
在默认的序列化实现中,会包括 非静态域 和 非瞬时域,与域的可见性声明没有关系,可能导致隐私信息泄露。
如果不想让某个字段被序列号,可以使用 transient
关键字,例如将上述代码修改为:
private transient String name;
这样的话,obj2.getName()
会返回 null
关于序列化版本号 serialVersionUID
private static final long serialVersionUID = 1L;
- 序列化版本号一致,向后兼容
- 序列化版本号不一致,向后不兼容
例如在类的版本号为1时将对象写入到字节序列,随后将类的版本号改成2,再从字节序列中读取对象,会抛出异常:
java.io.InvalidClassException: advanced.MyObj; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2