博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
序列化
阅读量:4050 次
发布时间:2019-05-25

本文共 4978 字,大约阅读时间需要 16 分钟。

-- Start

序列化是对象持久化的一种技术, 要想实现序列化, 我们只需要让类实现 Serializable 接口即可, 该接口中没有任何方法, 它只起到标识作用, 然后我们可以通过 ObjectOutputStream 将对象持久化到文件中, 或使用 ObjectInputStream 重新构造对象, 下面是一个简单的例子.

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.util.Date;public class Test {	public static void main(String[] args) throws Exception {		User u = new User("ShangBo", new Date());		// 序列化对象		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.dat"));		out.writeObject(u);		out.close();		// 反序列化对象		ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.dat"));		User nu = (User) in.readObject();		in.close();		// 打印姓名		System.out.println(nu.getName());	}}class User implements Serializable {	private String name;	private Date birthday;	public User() {	}	public User(String name, Date birthday) {		this.name = name;		this.birthday = birthday == null ? null : (Date) birthday.clone(); // 保护性 copy	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}	public Date getBirthday() {		return birthday == null ? null : (Date) birthday.clone(); // 保护性 copy	}	public void setBirthday(Date birthday) {		this.birthday = birthday == null ? null : (Date) birthday.clone(); // 保护性copy	}}
看上去序列化对象是非常简单的, 事实上它是一种非常复杂的技术, 有许多地方需要我们注意.

  1. 要序列化的类必须实现 Serializable 接口, 尽管它没有任何方法需要实现.
  2. 只有非 static 和非 transient 的域才会被序列化, 所以如果我们不想序列化某个域, 可以将它声明为 transient.
  3. 所有非 static 和非 transient 的域都必须都是可序列化的, 也就是说它们所属的类必须实现 Serializable 接口.
  4. 如果父类实现了 Serializable 接口, 那么它的所有子类都是可序列化的.
  5. 如果子类实现了 Serializable 接口, 那么子类在序列化的同时必须手动序列化父类的域且父类必须有无参构造方法, 如何手动序列化父类的域呢? 你很快就会知道.

我们可以在类中添加如下方法来干预序列化过程.

private void writeObject(java.io.ObjectOutputStream out) throws IOExceptionprivate void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundExceptionprivate void readObjectNoData() throws ObjectStreamExceptionANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
下面的例子演示了在父类没有实现 Serializable 接口时, 子类如何手动序列化父类的域.

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.util.Date;public class Test {	public static void main(String[] args) throws Exception {		User u = new User("ShangBo", new Date());		u.setID(123);		// 序列化对象		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.dat"));		out.writeObject(u);		out.close();		// 反序列化对象		ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.dat"));		User nu = (User) in.readObject();		in.close();		// 打印身份证号		System.out.println(nu.getID());	}}class Person {	private Integer ID;	// 父类必须有无参构造方法	public Person() {	}	public Integer getID() {		return ID;	}	public void setID(Integer iD) {		ID = iD;	}}class User extends Person implements Serializable {	private String name;	private Date birthday;	public User() {	}	public User(String name, Date birthday) {		this.name = name;		this.birthday = birthday == null ? null : (Date) birthday.clone(); // 保护性 copy	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}	public Date getBirthday() {		return birthday == null ? null : (Date) birthday.clone(); // 保护性 copy	}	public void setBirthday(Date birthday) {		this.birthday = birthday == null ? null : (Date) birthday.clone(); // 保护性copy	}	// 如下两个方法来干预序列化过程	// 反序列化的顺序必须和序列化的顺序一致	// 所以如果定义了 writeObject 方法, 我们必须同时定义 readObject 方法	private void writeObject(java.io.ObjectOutputStream out) throws IOException {		// 首先调用默认的方法序列化 User 类的所有域		out.defaultWriteObject();		// 其次手动序列化父类的域		out.writeObject(getID());	}	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {		// 反序列化的顺序必须和序列化的顺序一致		in.defaultReadObject();		setID((Integer) in.readObject());	}}
如果你觉得 Java 默认的序列化机制不能满足你的要求或你无法忍受它的速度, Java 为我们提供了自定义序列化机制的能力, 我们只需要实现 Externalizable 接口, 然后实现它的两个方法即可, 但是我们必须手动序列化所有的域, 包括父类的域. 同时我们还必须考虑对象引用的问题, 如果多个对象引用同一个对象, 反序列化之后它们还是引用同一个对象吗? 总之, 序列化机制是非常复杂的, 我们尽可能不要自己实现序列化机制, 而是利用 Java 为我们提供的序列化机制.

还有一个非常重要的问题需要考虑, 如果我们把一个对象序列化到一个文件中, 经过一段时间后该对象所属的类已经改变, 此时如果我们反序列化之前的对象会发生什么呢?毫无疑问, 此时应该抛出异常, Java 是如何检测类是否发生了变化呢? Java 会在序列化时自动为对象分配一个版本号, 该版本号是通过一定的算法计算出来的, 我们可以通过以下三种方式来获取版本号:

  • JDK 为我们提供了一个工具用来计算版本号, 感兴趣的读者可以到 JDK 的安装目录下查找 serialver.exe, 然后试着运行一下.
  • 我们也可以通过如下代码来计算某个类的版本号.
System.out.println(ObjectStreamClass.lookup(String.class).getSerialVersionUID());
  • 如果在 Eclipse 中, 一个类实现了 Serializable 接口而没有添加 serialVersionUID 域, Eclipse 会给我们一个警告信息, 点击这个警告信息 Eclipse 会为我们自动生成一个版本号, 而这个版本号和上面两种方法计算出来的版本号是一致的.

自动添加的这个版本号是非常敏感的, 对类的任何微小改动都有可能使版本号发生变化, 为此 Java 规范强烈推荐我们手动维护这个版本号, 我们可以在类中添加如下域来维护自己的版本号.

private static final long serialVersionUID = -3393497401990853857L;

---更多参见:

--
声 明:转载请注明出处
-- Last Updated on 2012-06-08
-- Written by ShangBo on 2012-06-08
-- End
你可能感兴趣的文章
网络视频服务器移植
查看>>
Encoding Schemes
查看>>
移植QT
查看>>
如此调用
查看>>
计算机的发展史
查看>>
带WiringPi库的交叉编译如何处理一
查看>>
带WiringPi库的交叉笔译如何处理二之软链接概念
查看>>
Spring事务的七种传播行为
查看>>
ES写入找不到主节点问题排查
查看>>
Java8 HashMap集合解析
查看>>
欢迎使用CSDN-markdown编辑器
查看>>
Android计算器实现源码分析
查看>>
Android系统构架
查看>>
Android 跨应用程序访问窗口知识点总结
查看>>
各种排序算法的分析及java实现
查看>>
SSH框架总结(框架分析+环境搭建+实例源码下载)
查看>>
自定义 select 下拉框 多选插件
查看>>
js获取url链接携带的参数值
查看>>
gdb 调试core dump
查看>>
gdb debug tips
查看>>