Serializable 详解(1):代码验证 Java 序列化与反序列化

阅读数:1 2020 年 3 月 26 日 18:59

Serializable详解(1):代码验证Java序列化与反序列化

说明:本文为 Serializable 详解(1),最后两段内容在翻译上出现歧义(暂时未翻译),将在后续的 Serializable(2)文中补充。
介绍:本文根据 JDK 英文文档翻译而成,本译文并非完全按照原文档字面文字直译,而是结合文档内容及个人经验翻译成更为清晰和易于理解的文字,并附加代码验证,帮助大家更好地理解 Serializable。

性质:接口类

package java.io

public interface Serializable

1.1 翻译文档

Serializability of a class is enabled by the class implementing the java.io.Serializable interface.

通过实现 java.io.Serializable interface 接口来序列化一个类。

Classes that do not implement this interface will not have any of their state serialized or deserialized.

没有实现此接口的类任何状态都不会序列化或反序列化。

All subtypes of a serializable class are themselves serializable.

可序列化类的所有子类而本身都是可序列化的。

The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.

序列化接口没有方法或字段域,它仅用来标识可序列化的语义。

(1)To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype’s public, protected, and (if accessible) package fields.
(2)The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class’s state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.
(3)During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.

(1) 为了让非序列化类的子类可以被序列化,这个子类可以承担保存和恢复超类的 pulic,protected,package 字段(如果可访问的话)。

(2) 只有当它拓展的类具有可访问无参构造函数来初始化类的状态时,子类才可以承担这样的责任。如果不是这种情况,就不能声明一个类是可序列化的。这个错误将在运行的时候被检测出来。

(3) 在反序列化期间,非序列化类的字段将通过类的以 public 或者 protected 修饰的空参构造函数实例化。无参数构造函数必须可访问可序列化的子类。序列化子类的字段能够从字符流里被还原。

1.2 辅助理解

(1)(2)(3) 三块主要说了三件事:

  • 非序列化的父类,其子类实现序列化时承担保存和恢复父类 public、protected、package 等子类可访问到子类的字段;
  • 非序列化的父类,其子类进行序列化时,父类需要有用 public 或者 protected 修饰的空参构造函数;
  • 若无空参构造函数的父类,其子类在运行序列化时将正常进行,但反序列化时会发生错误,并抛出异常。但父类有空参构造函数,子类完成序列化,父类属性却没有参与到序列化中。

1.3 注意:此处有三个坑。

  • (1)中所述父类未实现序列化,实现序列化的子类会承担保存和恢复父类的 public、protected、package 等子类可访问到子类的字段。此处我个人理解为实现序列化的子类进行序列化的时候继承了未实现序列化的父类中子类可访问到的属性,但序列化时无法记录下父类对象的状态信息;
  • 此处文档若要正确读取理解,切记(1)(2)(3)不可拆分,要放在一起去理解(上文之所以分开是便于翻译);
  • 此处英文翻译成汉字,难以理解其真实含义,所以通过下面的代码验证来辅助理解

1.4 代码验证

辅以 A/B 两套类型代码对比理解:

1)A 套

父类:Biology 类

复制代码
package com.springboot.SpringBootDemo.serializable;
public class Biology {
public String type;
private int num;
public Biology(String type, int num) {
this.type = type;
this.num = num;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}

子类:People 类

复制代码
package com.springboot.SpringBootDemo.serializable;
import java.io.Serializable;
public class People extends Biology implements Serializable{
private static final long serialVersionUID = -6623611040000763479L;
public String name;
protected String gender;
private int age;
public People(String type, int num, String name ,String gender ,int age) {
super(type, num);
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

测试类:

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
People pp = new People("human",10000," 张三 "," 男 ",25);
FileOutputStream fos = new FileOutputStream("test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(pp);
oos.flush();
oos.close();
// 反序列化
FileInputStream sfis = new FileInputStream("test.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
People p = (People) sois.readObject();
System.out.println(
p.getType() +" "+
p.getNum() +" "+
p.getName() +" "+
p.getGender() +" "+
p.getAge()
);
}
}

结果:

复制代码
Exception in thread "main" java.io.InvalidClassException: com.springboot.SpringBootDemo.serializable.People; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)
at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at com.springboot.SpringBootDemo.serializable.Test.main(Test.java:23)

结果说明:在序列化时未发生异常,而在反序列化 readObject() 时发生异常。也就是说,父类没有无参构造函数时,序列化正常进行,但反序列化时抛出 newInvalidClassException 异常。

2)B 套

父类:Person 类

复制代码
public class Person {
public String name;
public String gender;
public int age;
float height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
}

子类:Male 类

复制代码
import java.io.Serializable;
public class Male extends Person implements Serializable{
/**
*
*/
private static final long serialVersionUID = -7361904256653535728L;
public boolean beard;
protected String weight;
public boolean havaBeard(int age){
boolean flag = false;
if(age>=18){
flag = true;
}
return flag;
}
public boolean isBeard() {
return beard;
}
public void setBeard(boolean beard) {
this.beard = beard;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
}

测试类:

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SubTypeSerializable {
public static void main(String[] args) throws IOException, ClassNotFoundException{
/**Male 继承父类 Person,自身实现序列化接口,其父类 Person 没有实现序列化接口 */
FileOutputStream fos = new FileOutputStream("male.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Male male = new Male();
/**
* 其父类的父类 Person 的属性
*
* public String name;
public String gender;
public int age;
float height;
* */
male.setName(" 张三 ");
male.setGender(" 男性 ");
male.setAge(25);
male.setHeight(175);
/**
* 其自身属性
* public boolean beard;
* */
male.setBeard(true);
oos.writeObject(male);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("male.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Male ml = (Male) ois.readObject();
System.out.println(ml.getName() +" "+ml.getGender()+" "+ml.getHeight() +" "+ml.getAge()+" "+male.isBeard());
}
}

结果:

复制代码
ml.getName() == null
ml.getGender() == null
ml.getHeight() == 0.0
ml.getAge() == 0
male.isBeard() == true

1.5 测试分析

1)父类属性

public String name;

public String gender;

public int age;

float height;

其状态信息均未被记录;

2)自身属性

public boolean beard;

其状态信息被记录

2.1 翻译文档

When traversing a graph, an object may be encountered that does not support the Serializable interface. In this case the NotSerializableException will be thrown and will identify the class of the non-serializable object.

当循环遍历一个数据结构图(数据结构图可理解为数据结构类型,比如二叉树)的时候,对象可能会遭遇到不支持实现序列化接口的情景。在这种情况下,将发生抛出 NotSerializableException 异常,并且该类被定义为不可序列化类。

Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

在实现序列化和反序列化过程中,特殊处理的类需要实现这些特殊的方法:

复制代码
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;

The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The default mechanism for saving the Object’s fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.

witeObject 方法负责写入特定类的 Object 对象状态信息,readObject 方法可以还原该 Object 对象的状态信息。保存该 Object 对象字段的默认机制是通过调用 out.defaultWriteObject 来实现。该方法不需要关注属于其超类或子类的状态。通过使用 writeObject 方法将各个字段写入 ObjectOutputStream,或使用 DataOutput 支持的基本数据类型的方法来保存状态。

The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object’s non-static and non-transient fields. The defaultReadObject method uses information in the stream to assign the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved to add new fields. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.

readObject 方法是负责读取数据流并恢复该类的字段。它可以通过调用 in.defaultReadObject 来恢复非 static 以及非 transient 修饰的字段。defaultReadObject 方法通过数据流中的信息,把当前类保存在数据流中的字段信息分配到相对应的字段名(也就是说把字段的值分配给相对应的字段名)。这种处理方式也能处理该类新增字段的情况。该方法不需要关注属于其超类或子类的状态。通过使用 writeObject 方法将各个字段写入 ObjectOutputStream,或使用 DataOutput 支持的基本数据类型的方法来保存状态。通过 writeObject 方法把对象 Object 的各个字段写入到 ObjectOutputStream 中,或者通过使用 DataOutput 支持的基本数据类型的方法来保存该 Object 对象的状态信息。

The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance’s class than the sending party, and the receiver’s version extends classes that are not extended by the sender’s version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a “hostile” or incomplete source stream.

(该处翻译有些吃力,所以直接软件翻译,会后续进行代码验证体悟)

当出现反序列化与序列化类的版本不一致的情况时,readObjectNoData() 标签方法负责初始化对象的字段值。这种情况可能发生在反序列化时,接收方使用了发送方对象的类的不同版本,或者接收方继承的类的版本与发送方继承的类的版本不一致。另外,当序列化流被篡改了,也会发生这种情况。因此,当出现类不一致或者反序列化流不完全的情况时,readObjectNoData 初始化反序列化对象的字段就非常有用了。

2.2 代码验证

1)改变之前

复制代码
public class Cat implements Serializable{
/**
*
*/
private static final long serialVersionUID = -5731096200028489933L;
public String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

改变之前测试类:

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class CatFamilylTest {
public static void main(String[] args) throws Exception {
serializable();
deSerializable();
}
public static void serializable() throws Exception{
Cat cat = new Cat();
cat.setColor("white");
FileOutputStream fos = new FileOutputStream("catFamily.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(cat);
oos.flush();
oos.close();
}
public static void deSerializable() throws Exception{
FileInputStream sfis = new FileInputStream("catFamily.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Cat cat = (Cat) sois.readObject();
System.out.println(cat.getColor());
}
}

结果:white

2)第一次改变

第一次改变之增加父类:

复制代码
import java.io.Serializable;
public class CatFamily implements Serializable{
/**
*
*/
private static final long serialVersionUID = -7796480232179180594L;
public String catType;
public String getCatType() {
return catType;
}
public void setCatType(String catType) {
this.catType = catType;
}
private void readObjectNoData() {
this.catType = "tiger";
}
}

第一次改变之后之 Cat 类变化:

复制代码
public class Cat extends CatFamily{
/**
*
*/
private static final long serialVersionUID = -5731096200028489933L;
public String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

第一次改变之读取已经存在的 catFamily.txt 文件:

复制代码
public class CatFamilylTest {
public static void main(String[] args) throws Exception {
deSerializable();
}
public static void deSerializable() throws Exception{
FileInputStream sfis = new FileInputStream("catFamily.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Cat cat = (Cat) sois.readObject();
System.out.println(cat.getColor()+" <---->"+cat.getCatType());
}
}

第一次改变之结果:white <---->tiger

3)第二次改变测试

第二次改变之父类:

复制代码
public class CatFamily{
public String catType;
public String getCatType() {
return catType;
}
public void setCatType(String catType) {
this.catType = catType;
}
private void readObjectNoData() {
this.catType = "tiger";
}
}

第二次改变之 Cat 类:

复制代码
import java.io.Serializable;
public class Cat extends CatFamily implements Serializable{
/**
*
*/
private static final long serialVersionUID = -5731096200028489933L;
public String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

第二次改变之测试类:

复制代码
public class CatFamilylTest {
public static void main(String[] args) throws Exception {
deSerializable();
}
public static void deSerializable() throws Exception{
FileInputStream sfis = new FileInputStream("catFamily.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Cat cat = (Cat) sois.readObject();
System.out.println(cat.getColor()+" <---->"+cat.getCatType());
}
}

第二次改变之结果:white <---->null

4)第三次改变举例对比验证

第三次改变之抛弃父类,且 Cat 类改变:

复制代码
import java.io.Serializable;
public class Cat implements Serializable{
/**
*
*/
private static final long serialVersionUID = -5731096200028489933L;
public String type;
public String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
private void readObjectNoData() {
this.type = "hellokitty";
}
}

第三次改变之测试类:

复制代码
public class CatFamilylTest {
public static void main(String[] args) throws Exception {
deSerializable();
}
public static void deSerializable() throws Exception{
FileInputStream sfis = new FileInputStream("catFamily.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Cat cat = (Cat) sois.readObject();
System.out.println(cat.getColor()+" <---->"+cat.getType());
}
}

第三次改变之测试结果:white <---->null

2.3 测试代码描述

1)第一种(改变之前)

描述:建立实现序列化接口的 Cat 类,以及对应的测试类生产文件 catFamily.txt

两个目的:

  • 用于建立变化的基础层代码;
  • 生成序列化后的文件。

结果:反序列化 catFamily.txt 文件,得出正常结果 write 。

2)第二种(第一次改变对比未改变)

改变之处:

  • 增加实现序列化接口的父类 CatFamily 类,增添 readObjectNoData() 方法,并且设置属性字段 catType 值为 tiger;
  • Cat 类不直接实现序列化 Serializable 接口,而是继承 CatFamily 类;
  • 测试类对 catFamily.txt 进行反序列化读取。

目的:验证 readObjectNoData() 标签方法结果。

结果:反序列化 catFamily.txt 文件,得出结果 white <---->tiger。

总结:实现 readObjectNoData() 标签方法。

3)第三种(第二次改变对比第一次改变)

改变之处:

  • 改变父类 CatFamily 类,去掉实现序列化 Serializable 接口;
  • 子类 Cat 类依然继承父类 CatFamily 类,并且直接实现序列化 Serializable 接口;
  • 测试类对 catFamily.txt 进行反序列化读取。

目的:验证父类未实现序列化 Serializable 接口时,readObjectNoData() 方法是否继续有效。

结果:反序列化 catFamily.txt 文件,得出结果 white <---->null 。

总结:readObjectNoData() 方法没有得到体现。

4)第四种(第三次改变对比未改变)

改变之处:

  • Cat 类去掉父类 CatFamily 类,自身直接实现序列化 Serializable 接口;
  • Cat 类实现 readObjectNoData() 方法;
  • 测试类对 catFamily.txt 进行反序列化读取。

目的:测试 readObjectNoData() 方法的作用域。

结果:反序列化 catFamily.txt 文件,得出结果 white <---->null。

总结:readObjectNoData() 方法作用域为写入 catFamily.txt 文件的对象 Object 的实体类的实现序列化 Serializable 接口的父类。

2.4 推测总结:

  • readObjectNoData() 标签方法作用域为进行序列化对象的父类,并且其父类必须实现了序列化接口 Serializable;
  • readObjectNoData() 标签方法在上面测试的代码中体现作用类似于 set 属性;
  • readObjectNoData() 标签方法内 set 的属性值为该类的属性值,也就是说当引用其他对象属性值进行 set 时,该方法是无效的。

3.1 翻译文档

Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

实现序列化的类,其 Object 对象被指定另外一个实现序列化非此类的对象进行替换的时候,在进行把该实体 Object 对象写入到数据流中时,需要实现 Object writeReplace() throws ObjectStreamException; 这个特殊的标签方法。

3.2 代码验证

注意:替换类和被替换类都需要实现序列化接口,否则在写入(writeObject)时会抛出 java.io.NotSerializableException 异常,且被替换类为彻底被替换。

1)测试 writeReplace() 标签方法

实体类:

复制代码
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Dog implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4094903168892128473L;
private String type;
private String color;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
private Object writeReplace() throws ObjectStreamException {
Wolf wolf = new Wolf();
wolf.setType(type);
wolf.setColor(color);
return wolf;
}
}
class Wolf implements Serializable{
/**
*
*/
private static final long serialVersionUID = -1501152003733531169L;
private String type;
private String color;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

测试类:

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class DogTest {
public static void serializable() throws IOException{
Dog dog = new Dog();
dog.setColor("white");
dog.setType("Chinese garden dog");
FileOutputStream fos = new FileOutputStream("dog.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(dog);
oos.flush();
oos.close();
}
public static void deSerializable() throws IOException,ClassNotFoundException{
FileInputStream sfis = new FileInputStream("dog.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Wolf wolf = (Wolf) sois.readObject();
System.out.println(wolf.getType() +"<------->"+ wolf.getColor());
}
public static void main(String[] args) throws IOException ,ClassNotFoundException{
serializable();
deSerializable();
}
}

代码实现结果:Chinese garden dog<------->white。

2)测试是否被彻底替换

代码说明:实体类不修改,只修改测试类的反序列化方法,在 readObject() 方法时由 Wolf 对象转变为 Dog 对象。

复制代码
public static void deSerializable() throws IOException,ClassNotFoundException{
FileInputStream sfis = new FileInputStream("dog.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Dog dog = (Dog) sois.readObject();
System.out.println(dog.getType() +"<------->"+ dog.getColor());
}

代码实现结果:

复制代码
25 行:Dog dog = (Dog) sois.readObject();
32 行:deSerializable();
Exception in thread "main" java.lang.ClassCastException: com.springboot.SpringBootDemo.serializable.Wolf cannot be cast to com.springboot.SpringBootDemo.serializable.Dog
at com.springboot.SpringBootDemo.serializable.DogTest.deSerializable(DogTest.java:25)
at com.springboot.SpringBootDemo.serializable.DogTest.main(DogTest.java:32)

序列化对象为 Dog 对象,而反序列化依然通过 Dog 对象,结果发生异常,此时可知在序列化时 Dog 对象被 Wolf 对象给替换了。

4.1 翻译文档

This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private,protected and package-private access. Subclass access to this method follows java accessibility rules.

在序列化对象时,其类的方法中如果有 writeReplace 标签方法存在的话,则该标签方法会在序列化写入时被调用。因此该方法可以具有 private,protected 和 package-private 访问。该类的子类访问该方法时会遵循 java 可访问性规则。

4.2 代码验证

注意:

  • 父类实现 writeReplace 标签方法;
  • 子类拥有访问 writeReplace 标签方法的权限。

1)实体类

复制代码
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Dog implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4094903168892128473L;
private String type;
private String color;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Object writeReplace() throws ObjectStreamException {
Wolf wolf = new Wolf();
wolf.setType(type);
wolf.setColor(color);
return wolf;
}
}
class ChineseGardenDog extends Dog {
private float height;
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
}
class Wolf implements Serializable{
/**
*
*/
private static final long serialVersionUID = -1501152003733531169L;
private String type;
private String color;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

2)测试类

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class DogTest {
public static void serializable() throws IOException{
ChineseGardenDog dog = new ChineseGardenDog();
dog.setColor("white");
dog.setType("Chinese garden dog");
dog.setHeight(55);
FileOutputStream fos = new FileOutputStream("dog.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(dog);
oos.flush();
oos.close();
}
public static void deSerializable() throws IOException,ClassNotFoundException{
FileInputStream sfis = new FileInputStream("dog.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Wolf wolf = (Wolf) sois.readObject();
System.out.println(wolf.getType() +"<------->"+ wolf.getColor());
}
public static void main(String[] args) throws IOException ,ClassNotFoundException{
serializable();
deSerializable();
}
}

测试结果:Chinese garden dog<------->white。

5.1 翻译文档

Classes that need to designate a replacement when an instance of it is read from the stream should implement this special method with the exact signature.
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
This readResolve method follows the same invocation rules and accessibility rules as writeReplace.

当从数据流中读取一个实例的时候,指定替换的类需要实现此特殊方法。

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

readResolve 标签方法遵循与 writeReplace 相同的调用规则和可访问性规则。

5.2 代码验证

注:该方法的写入对象实例和读取对象实例为同一个对象(适用于单例模式)。

1)未实现标签方法

实体类:

复制代码
import java.io.Serializable;
public class Mouse implements Serializable{
/**
*
*/
private static final long serialVersionUID = -8615238438948214201L;
private String name;
public static Mouse INSTANCE;
public static Mouse getInstance(){
if(INSTANCE == null){
INSTANCE = new Mouse();
}
return INSTANCE;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

测试类:

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MouseTest {
public static void serializable() throws IOException{
Mouse mouse= Mouse.getInstance();
mouse.setName("Jerry");
FileOutputStream fos = new FileOutputStream("mouse.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
System.out.println(" 写入对象 hash 值 = "+ mouse.hashCode());
oos.writeObject(mouse);
oos.flush();
oos.close();
}
public static void deSerializable() throws IOException,ClassNotFoundException{
FileInputStream sfis = new FileInputStream("mouse.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Mouse mouse = (Mouse) sois.readObject();
System.out.println(" 读取对象 hash 值 = " +mouse.hashCode());
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
serializable();
deSerializable();
}
}

测试结果:

写入对象 hash 值 = 366712642

读取对象 hash 值 = 1096979270

2)实现标签方法:(测试类不变,实体类增加 readResolve() 方法)

实体类:

复制代码
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Mouse implements Serializable{
/**
*
*/
private static final long serialVersionUID = -8615238438948214201L;
private String name;
public static Mouse INSTANCE;
public static Mouse getInstance(){
if(INSTANCE == null){
INSTANCE = new Mouse();
}
return INSTANCE;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
}

测试结果:

写入对象 hash 值 = 366712642

读取对象 hash 值 = 366712642

推测:指定写入的对象实例和读取指定的对象实例为同一个。

6.1 翻译文档

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender’s class, then deserialization will result in an {@link InvalidClassException}. A serializable class can declare its own serialVersionUID explicitly by declaring a field named "serialVersionUID" that must be static, final, and of type long:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java™ Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class–serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

6.2 针对实现 Serializable 接口 代码验证

父类:Person 类

复制代码
public class Person {
public String name;
public String gender;
public int age;
float height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
}

子类:Male 类

复制代码
import java.io.Serializable;
public class Male extends Person implements Serializable{
/**
*
*/
private static final long serialVersionUID = -7361904256653535728L;
public boolean beard;
public boolean havaBeard(int age){
boolean flag = false;
if(age>=18){
flag = true;
}
return flag;
}
public boolean isBeard() {
return beard;
}
public void setBeard(boolean beard) {
this.beard = beard;
}
}

三级子类:Students 类

复制代码
public class Students extends Male{
private static final long serialVersionUID = -6982821977091370834L;
public String stuCard;
private int grades;
public String getStuCard() {
return stuCard;
}
public void setStuCard(String stuCard) {
this.stuCard = stuCard;
}
public int getGrades() {
return grades;
}
public void setGrades(int grades) {
this.grades = grades;
}
}

类:Female 类

复制代码
import java.io.Serializable;
public class Female implements Serializable{
private static final long serialVersionUID = 6907419491408608648L;
public String name;
public String gender;
public int age;
float height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
}

测试类:SubTypeSerializable

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SubTypeSerializable {
public static void main(String[] args) throws IOException, ClassNotFoundException{
/**(一)、Person 实体类,未实现序列化接口,无父类 */
FileOutputStream fo = new FileOutputStream("person.txt");
ObjectOutputStream ss = new ObjectOutputStream(fo);
Person person = new Person();
person.setAge(100);
person.setGender(" 性别 ");
person.setHeight(165);
person.setName(" 人类 ");
ss.writeObject(person);
ss.flush();
ss.close();
// 反序列化
FileInputStream sfis = new FileInputStream("person.txt");
ObjectInputStream sois = new ObjectInputStream(sfis);
Person ps = (Person) sois.readObject();
System.out.println(ps.getName() +" "+ps.getGender()+" "+ps.getHeight() +" "+ps.getAge());
/** 结果:
在执行 writeObject(person) 是发生异常
Exception in thread "main" java.io.NotSerializableException:
com.springboot.SpringBootDemo.serializable.Person
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:21)
Exception in thread "main" java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.springboot.SpringBootDemo.serializable.Person
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:28)
* */
System.out.println("<--------------------------------------------------------------------------->");
/**(二)、Male 继承父类 Person,自身实现序列化接口,其父类 Person 没有实现序列化接口 */
FileOutputStream fos = new FileOutputStream("male.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Male male = new Male();
/**
* 其父类的父类 Person 的属性
*
* public String name;
public String gender;
public int age;
float height;
* */
male.setName(" 张三 ");
male.setGender(" 男性 ");
male.setAge(25);
male.setHeight(175);
/**
* 其自身属性
* public boolean beard;
* */
male.setBeard(true);
oos.writeObject(male);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("male.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Male ml = (Male) ois.readObject();
System.out.println(ml.getName() +" "+ml.getGender()+" "+ml.getHeight() +" "+ml.getAge()+" "+male.isBeard());
/** 结果:
* 父类没有被序列化,唯独子类属性被序列化
*
* ml.getName() == null
* ml.getGender() == null
* ml.getHeight() == 0.0
* ml.getAge() == 0
* male.isBeard() == true
* 父类属性:
* public String name;
public String gender;
public int age;
float height;
均未实现序列化;
自身属性:
public boolean beard;
实现序列化
* */
System.out.println("<--------------------------------------------------------------------------->");
/**(三)、Female 实现序列化接口,无父类 */
FileOutputStream ffos = new FileOutputStream("female.txt");
ObjectOutputStream foos = new ObjectOutputStream(ffos);
Female female = new Female();
/**
* 其自身的属性
* public String name;
public String gender;
public int age;
float height;
**/
female.setAge(25);
female.setGender(" 女性 ");
female.setHeight(165);
female.setName(" 张芳 ");
foos.writeObject(female);
foos.flush();
foos.close();
// 反序列化
FileInputStream ffis = new FileInputStream("female.txt");
ObjectInputStream fois = new ObjectInputStream(ffis);
Female fm = (Female) fois.readObject();
System.out.println(fm.getName() +" "+fm.getGender()+" "+fm.getHeight() +" "+fm.getAge());
/** 结果:
* 自身属性均实现序列化
*
* fm.getName() == 张芳
* fm.getGender() == 女性
* fm.getHeight() == 165.0
* fm.getAge() == 25
* 所有属性均实现序列化 */
System.out.println("<--------------------------------------------------------------------------->");
/**(四)、Students 未实现序列化接口,继承父类 Male,其父类继承父类 Person,自身实现序列化接口,其父类 Person 没有实现序列化接口 */
FileOutputStream stufos = new FileOutputStream("students.txt");
ObjectOutputStream stuoos = new ObjectOutputStream(stufos);
Students students = new Students();
/**
* 其父类的父类 Person 的属性
*
* public String name;
public String gender;
public int age;
float height;
* */
students.setName(" 王小明 ");
students.setGender(" 男性 ");
students.setAge(15);
students.setHeight(160);
/**
* 其父类 Male 属性
* public boolean beard;
* */
students.setBeard(true);
/**
* 自身属性
* public String stuCard;
private int grades;
* */
students.setStuCard("1234567890987");
students.setGrades(300);
stuoos.writeObject(students);
stuoos.flush();
stuoos.close();
// 反序列化
FileInputStream stufis = new FileInputStream("students.txt");
ObjectInputStream stuois = new ObjectInputStream(stufis);
Students st = (Students) stuois.readObject();
System.out.println(st.getName() +" "+st.getGender()+" "+st.getAge()+" "+st.getHeight()+" "+st.isBeard()+" "+st.getStuCard()+" "+st.getGrades());
/** 结果:
* 父类的父类属性未实现序列化,父类实现序列化,自身实现序列化
* st.getName() == null
* st.getGender() == null
* st.getAge() == 0
* st.getHeight() == 0.0
* st.isBeard() == true
* st.getStuCard() == 1234567890987
* st.getGrades() == 300
* 自身 public String stuCard;
private int grades;
实现序列化;
而父类 Male 属性
public boolean beard
实现序列化;
父类的父类 Person
public String name;
public String gender;
public int age;
float height;
未实现序列化
* */
}
}

6.3 回顾

1)在使用 ObjectInputStream、ObjectOutputStream 对对象进行写入写出时,其写入的对象的类需要实现 java.io.Serializable 序列化接口,否则会报出 writeObject() 异常:

复制代码
Exception in thread "main" java.io.NotSerializableException: com.springboot.SpringBootDemo.serializable.Person
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:21)
readObject() 异常:
Exception in thread "main" java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.springboot.SpringBootDemo.serializable.Person
java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:28)

2)父类未实现 java.io.Serializable 序列化接口,其子类依然可以进行序列化,但其子类进行对象序列化读写时,父类无法被序列化,只能自身实现序列化;

3)自身实现 java.io.Serializable 序列化接口,在进行对象读写时会被实现序列化;

4)父类实现 java.io.Serializable 序列化接口,其子类不需要再次申明实现序列化,子类在进行对象序列化读写时,父类和子类均被实现序列化。

7.1 总结

1)java.io.Serializable 接口

首先,Serializable 类是一个接口,所以对象的序列化并不是 Serializable 来实现的;

其次,Serializable 是一个标签,各种序列化类在读取到这个标签的时候,会按照自己的方式进行序列化。

2)序列化是干什么的,为什么需要序列化

我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等,而这些数据都会以二进制序列的形式在网络上传送。

那么当两个 Java 进程进行通信时,能否实现进程间的对象传送呢?答案是可以的!如何做到呢?这就需要 Java 序列化与反序列化了!

换句话说:一方面,发送方需要把这个 Java 对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。

当我们明晰了为什么需要 Java 序列化和反序列化后,我们很自然地会想 Java 序列化的好处。

  • 实现了数据的持久化,通过序列化可以记录下数据结构或者对象的状态(也就是实体变量 Object[注:不是 Class] 某一个时间点的值),把数据临时或者永久地保存到硬盘上(通常存放在文件里);
  • 利用序列化实现远程通信,并且在数据传递过程中或者使用时能够保证数据结构或者对象状态的完整性和可传递性,即在网络上传送对象的字节序列。

评论

发布