现在的位置: 首页 > 综合 > 正文

序列化

2012年04月30日 ⁄ 综合 ⁄ 共 4425字 ⁄ 字号 评论关闭

-- 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 IOException

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException

private void readObjectNoData() throws ObjectStreamException

ANY-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;

---更多参见:Java 精萃

-- 声 明:转载请注明出处
-- Last Updated on 2012-06-08
-- Written by ShangBo on 2012-06-08
-- End

抱歉!评论已关闭.