java序列化接口Serializable

Serializable接口说明

类的可序列化性通过实现(implements) java.io.Serializable可序列化接口。 没有实现这个接口的类不会将其任何状态序列化或反序列化。 可序列化类的所有子类型本身可序列化。 序列化接口没有方法或字段只用于识别可序列化的语义。

为了允许序列化不可序列化类的子类型,子类型可以承担保存和还原父类型的公开状态(public),受保护状态(protected)和(如果可以访问)包字段。 只有在以下情况下,子类型才能承担此责任:扩展的类具有可访问的无参(no-arg)构造函数,用于初始化类的状态。 如果不是这种情况,声明出来的序列化类是错误的, 错误将会在运行时(runtime)被检测到。

在反序列化期间,不可序列化类的字段将使用公开的(public)或受保护的(protected)无参(no-arg)构造函数进行初始化。 子类必须可访问无参数构造函数那是可序列化的。 可序列化子类的字段将从流中恢复。

在遍历类字段时,可能会遇到一个对象,该对象不会支持序列化Serializable接口。 在这种情况下会抛出NotSerializableException并将识不可序列化的对象类名称。

类在序列化和反序列化过程必须执行具有这些确切要求的特殊方法签名:

     1、private void writeObject(java.io.ObjectOutputStream out)throws IOException

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

            3、private void readObjectNoData( ) throws ObjectStreamException;

writeObject方法负责写入对象,以便readObject方法可以还原它。默认的保存机制可以通过调用来调用对象的字段out.defaultWriteObject。该方法不需要考虑它自身的状态属于它的超类(superclasses)或子类(subclasses)。通过使用writeObject方法或使用DATAOutput支持的原始数据类型将各个字段状态保存到ObjectOutputStream。

readObject方法负责从流中读取并恢复类类属性字段。它可以调用in.defaultReadObject来调用恢复对象的非静态(non-static)和非瞬态(non-transient)字段。defaultReadObject方法使用流中的信息将保存在流中的对象的字段与当前对象中相应命名的字段一起分配。当类添加新字段的时候,会发生这种情况。 该方法不需要关注属于其超类(superclasses)或子类(subclasses)的状态。通过使用writeObject方法或使用DATAOutput支持的原始数据类型将各个字段状态保存到ObjectOutputStream。

readObjectNoData方法负责在序列化流没有将给定类列为反序列化对象的超类的情况下初始化其特定类的对象状态。当接收方使用与发送方不同的反序列化实例类版本,并且接收方的版本扩展了发送方版本未扩展的类时,可能会发生这种情况。如果序列化流被篡改,也可能发生这种情况;因此,readObjectNoData对于正确初始化反序列化对象非常有用,尽管源流“恶意”或不完整。

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

             ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

如果方法存在,并且可以从中定义的方法访问正在序列化的对象的类。因此,该方法可以具有私有(private)、受保护(protected)和包私有(package-private)访问。对这个方法的子类访问遵循java可访问性规则。

当从流中读取替换的实例时,需要指定替换的类应该用精确的签名实现这个特殊方法:

            ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

            此readResolve方法遵循相同的调用规则和可访问性规则为writeReplace。

序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。如果接收方已为具有不同对象的对象加载了一个类serialVersionUID比相应发送者的类要大,则反序列化将导致InvalidClassException。 一个可序列化的类可以通过以下方式显式声明其自己的serialVersionUID,声明一个名为serialVersionUID 字段,该字段必须为静态(static),不可变(final),且类型为long。

            ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可序列化的类没有显示声明serialVersionUID,则序列化运行时将根据Java对象序列化规范计算默认的serialVersionUID值基于类的各个方面。但是,强烈建议,所有可序列化的类都显示声明serialVersionUID值,因为默认的serialVersionUID计算可能因编译器实现不同使类详细信息高度敏感,导致在反序列化期间意外 InvalidClassException。因此,在不同的Java编译器中保证一致的serialVersionUID值实现时,可序列化的类必须声明一个显式的serialVersionUID值。还强烈建议明确serialVersionUID声明使用private修饰符,其中可能,因为此类声明仅适用于立即声明class--serialVersionUID字段不能用作继承成员。数组类不能声明显式的serialVersionUID,因此它们始终具有默认计算值,但需要匹配数组类将免除serialVersionUID值。

以上内容翻译自jdk中java.io.Serializable接口声明注释,以上内容做简单总结,可以整理为如下几点:

  • 序列化和反序列类必须实现Serializable接口
  • 序列化类的引用类型的属性类必须实现Serializable接口
  • 序列化类须有无参构造方法
  • writeObject方法负责写入对象;readObject方法负责还原对象
  • 每个可序列化类都有一个serialVersionUID,建议显示声明,使用static fianl long 修饰符修饰

Serializable代码示例

序列化与反序列化

定义Student类,Student类实现Serializable接口,以便Student对象可以序列化与反序列化。

import java.io.Serializable;

public class Student implements Serializable{

	private String name;
	
	private int age;
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String toString() {
		return "[name:" + name + ";age:" + age + "]";
	}
}

这里的Student类实现(implements)了Serializable接口,但是没有定义serialVersionUID属性。

main方法测试

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableMain {

	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		/**
		 * 序列化 writeObject方法写对象
		 */
		Student stu = new Student("zhangsan", 15);
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
		oos.writeObject(stu);
		oos.close();
		
		/**
		 * 反序列化 readObject方法还原对象
		 */
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"));
	    Student stuSerializable = (Student) ois.readObject();
	    ois.close();
	    System.out.println(stuSerializable);
	}
}

序列化后,可以在工程根目录下看到对象文件stu.txt,打开后可以看到序列化对象stu的一些信息,由于是序列化后的对象,这里打开看到的是乱码。

反序列化后,在控制台,可以看到,打印出的stuSerializable对象信息。

[name:zhangsan;age:15]

至此,序列化与反序列化成功。

上面的Student类定义中,没有显示声明serialVersionUID属性,成功序列化与反序列化。但这个从编码规范和序列化安全角度,不提倡,还是显示声明serialVersionUID属性。

public class Student implements Serializable{

	/**
	 * 序列化类版本id
	 */
	private static final long serialVersionUID = 6384476034643864037L;

transient关键字

transient关键字在Java中修饰临时属性,被transient关键字修饰的属性不会序列化对数据流中。

import java.io.Serializable;

public class Student implements Serializable{

	private String name;
	
	private transient int age;
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String toString() {
		return "[name:" + name + ";age:" + age + "]";
	}
}

再通过main方法测试,可以看到,反序列化得到的age为0,这里age为0原因即transient关键字修饰了age属性,age属性作为一个临时属性,在序列化的时候未序列化到数据流中。

[name:zhangsan;age:0]

对象属性序列化

在原有Student属性中增加一个对象属性Address,Address属性未实现implements java.io.Serialzable接口

public class Address {

	// 省
	private String provice;
	
	public Address(String provice) {
		this.provice = provice;
	}
	
	public String toString() {
		return "[provice:"+ provice+"]";
	}
}
import java.io.Serializable;

public class Student implements Serializable{

	/**
	 * 序列化类版本id
	 */
	private static final long serialVersionUID = 6384476034643864037L;

	private String name;
	
	private int age;
	
	private Address address;
	
	public Student(String name, int age, Address address) {
		this.name = name;
		this.age = age;
		this.address = address;
	}
	
	public String toString() {
		return "[name:" + name + ";age:" + age + ";address:" + address.toString() + "]";
	}
}

main方法测试

public class SerializableMain {

	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		/**
		 * 序列化 writeObject方法写对象
		 */
		Student stu = new Student("zhangsan", 15, new Address("China.Taiwan"));
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
		oos.writeObject(stu);
		oos.close();
	}
}

控制台抛异常,Exception in thread "main" java.io.NotSerializableException: com.serialize.Address,异常信息明确Address类没有序列化

Exception in thread "main" java.io.NotSerializableException: com.serialize.Address
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.serialize.SerializableMain.main(SerializableMain.java:18)

在给Address类添加序列化属性

public class Address implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 5879834545307777452L;

再通过main方法测试,可以得到序列化之前的对象信息

[name:zhangsan;age:15;address:[provice:China.Taiwan]]

猜你喜欢

转载自blog.csdn.net/magi1201/article/details/113375334