欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

对象序列化  

程序员文章站 2022-07-16 09:09:38
...

当你创建对象时,只要你需要,他就会一直存在,但是在程序终止时,无论如何他都不会继续存在。
尽管这么做肯定是有意义,但是仍旧存在某些情况,如果对象能够在程序不运行的情况下仍能存在并保存其信息,那将非常有用。
这样,在下次运行时,该对象将被重建并且拥有的信息与程序上次运行时所拥有的信息相同。




Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并以后能够将这个字节序列完全恢复成原来的对象。
这一过程甚至可通过网络进行;这意味序列化机制能弥补不同操作系统之间的差异。




要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这是,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。
要反向进行该过程,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。




Demo:
下面这个例子通过对链接的对戏那个生成一个worm(蠕虫)对序列化机制进行测试。

package cn.partner4java.serializable;

import java.io.Serializable;

public class Data implements Serializable{
	private int n;
	public Data(int n) {
		this.n = n;
	}
	
	@Override
	public String toString() {
		return n+"";
	}
}


package cn.partner4java.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Random;

public class Worm implements Serializable {
	private static Random random = new Random(47);
	private Data[] d = {
			new Data(random.nextInt(10)),
			new Data(random.nextInt(10)),
			new Data(random.nextInt(10))
	};
	private Worm next;
	private char c;
	public Worm(int i,char x) {
		System.out.println("Worm constructor: " + i);
		c = x;
		if(--i > 0){
			next = new Worm(i, (char)(x+1));
		}
	}
	
	public Worm() {
		System.out.println("Default constructor");
	}

	@Override
	public String toString() {
		return "Worm [d=" + Arrays.toString(d) + ", next=" + next + ", c=" + c
				+ "]";
	}
	
	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		Worm worm = new Worm(6, 'a');
		System.out.println(worm);
		
		ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("worm.out"));
		outputStream.writeObject("Worm storage\n");
		outputStream.writeObject(worm);
		outputStream.close();
		
		ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("worm.out"));
		String s = (String) inputStream.readObject();
		Worm w = (Worm) inputStream.readObject();
		System.out.println("s: " + s + w);
		
		
		
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
		ObjectOutputStream outputStream2 = new ObjectOutputStream(arrayOutputStream);
		outputStream2.writeObject("Worm storage\n");
		outputStream2.writeObject(worm);
		outputStream2.flush();
		
		ObjectInputStream inputStream2 = new ObjectInputStream(new ByteArrayInputStream(
				arrayOutputStream.toByteArray()));
		String s2 = (String)inputStream2.readObject();
		Worm w2 = (Worm)inputStream2.readObject();
		System.out.println("S2: " + s2 + w2);
	}
}







public interface Serializable类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。


要允许不可序列化类的子类型序列化,可以假定该子类型负责保存和还原超类型的公用 (public)、受保护的 (protected) 和(如果可访问)包 (package) 字段的状态。仅在子类型扩展的类有一个可访问的无参数构造方法来初始化该类的状态时,才可以假定子类型有此责任。如果不是这种情况,则声明一个类为可序列化类是错误的。该错误将在运行时检测到。


在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数的构造方法。可序列化子类的字段将从该流中还原。


当遍历一个图形时,可能会遇到不支持可序列化接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。


在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:




private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。


readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。


将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:




ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。


在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。




ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。


序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:




ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修改器显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于立即声明类 -- serialVersionUID 字段作为继承成员没有用处。










序列化的控制


有些时候,对象的某一个部分,不想被序列化。
Externalizable:
public interface Externalizableextends Serializable
Externalizable 实例类的惟一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替自定义的 writeObject 和 readObject 方法实现。
Serialization 对象将使用 Serializable 和 Externalizable 接口。对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持 Externalizable 接口。如果对象支持 Externalizable,则调用 writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream 保存该对象。
在重构 Externalizable 对象时,先使用无参数的公共构造方法创建一个实例,然后调用 readExternal 方法。通过从 ObjectInputStream 中读取 Serializable 对象可以恢复这些对象。
Externalizable 实例可以通过 Serializable 接口中记录的 writeReplace 和 readResolve 方法来指派一个替代对象。
方法摘要
void readExternal(ObjectInput in)
对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput 的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。
void writeExternal(ObjectOutput out)
该对象可实现 writeExternal 方法来保存其内容,它可以通过调用 DataOutput 的方法来保存其基本值,或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。






transienct(瞬时)关键字
当我们对序列化进行控制时,可能某个特定子对象不想让Java的序列化机制自动保存与恢复。如果子对象标识的是我们不希望将序列化的敏感信息,通常就会面临这种情况。