Java 序列化

作者ChihMinh,原作链接https://chihminh.github.io/2016/08/10/serializable/,转载请注明出处

什么是序列化?

将对象编码为字节流,该字节流能描述对象的类、版本信息、内部状态。最重要的一点就是对象的内部状态,即对象的所有fields,但不包括transient和static修饰的域。一言以蔽之:序列化即以某种媒介存储(以字节流的形式)对象的状态信息,反序列化即从字节流恢复状态信息。

为什么要序列化?

参考内容原文见这里

  • Communication: If you have two machines that are running the same code, and they need to communicate, an easy way is for one machine to build an object with information that it would like to transmit, and then serialize that object to the other machine. It’s not the best method for communication, but it gets the job done.
  • Persistence: If you want to store the state of a particular operation in a database, it can be easily serialized to a byte array, and stored in the database for later retrieval.
  • Deep Copy: If you need an exact replica of an Object, and don’t want to go to the trouble of writing your own specialized clone() class, simply serializing the object to a byte array, and then de-serializing it to another object achieves this goal.
  • Caching: Really just an application of the above, but sometimes an object takes 10 minutes to build, but would only take 10 seconds to de-serialize. So, rather than hold onto the giant object in memory, just cache it out to a file via serialization, and read it in later when it’s needed.
  • Cross JVM Synchronization: Serialization works across different JVMs that may be running on different architectures.
  • 通信:如果两台机器运行相同的代码需要进行通信,可以把一个对象序列化然后传送到另一台机器,尽管这不是通信的最好方法。
  • 持久化:将对象保存到文件中或者数据库中。
  • 深复制:不需要自己重写Object.clone()方法,只需要序列化和反序列化就能做到
  • 缓存:如果生成某个对象很费时,而反序列化又很快,那么完全可以将一个对象序列化存到本地文件,在需要的时候进行反序列化。
  • 跨JVM同步:序列化与反序列化都是JVM独立的,也就是可以在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

总结为:将对象编码为字节流写进文件或者在不同的JVM之间传输

怎样序列化?

一个类实现Serializable接口即可序列化,Serializable接口没有任何变量和方法

1
2
public interface Serializable {
}

static及transicnt修饰的变量

static

1
2
3
4
5
6
7
8
9
10
public class Human implements Serializable {
private static final long serialVersionUID = 1L;
private int age;
public static String staticField;
public final String finalField = "final";
public static final String staticFinalField ="static&final";
public transient String transientField;
// 省略private属性的setter和getter方法
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class Test {
public static void main(String[] args) {
// 设置序列化之前的属性
Human human = new Human();
human.setAge(10);
Human.staticField = "static_before_serialize";
human.transientField = "transient";
System.out.println("序列化之前的对象:");
System.out.println("Age is: " + human.getAge());
System.out.println("Static field is: " + Human.staticField);
System.out.println("Final field is: " + human.finalField);
System.out.println("Static final field is : " + Human.staticFinalField);
System.out.println("Transient field is: " + human.transientField);
//序列化
File file = new File("out.txt");
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(file);
oos = new ObjectOutputStream(fos);
oos.writeObject(human);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
fos.close();
} catch (IOException e) {
System.out.println("关闭写入失败");
}
}
// 序列化之后反序列化之前改变静态变量的值
Human.staticField = "static_after_serialize";
// 进行反序列化
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(file);
ois = new ObjectInputStream(fis);
try {
Human human01 = (Human)ois.readObject();
System.out.println("反序列化之后的对象:");
System.out.println("Age is: " + human01.getAge());
System.out.println("Static field is: " + Human.staticField);
System.out.println("Final field is: " + human01.finalField);
System.out.println("Static final field is: " + Human.staticFinalField);
System.out.println("Transient field is: " + human01.transientField);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ois.close();
fis.close();
} catch (IOException e) {
System.out.println("关闭读取失败");
}
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
序列化之前的对象:
Age is: 10
Static field is: static_before_serialize
Final field is: final
Static final field is : static&final
Transient field is: transient
反序列化之后的对象:
Age is: 10
Static field is: static_after_serialize
Final field is: final
Static final field is: static&final
Transient field is: null

可以看到反序列化之后的static变量已经改变,transient关键字修饰的变为默认的null。事实上就是序列化过程中不会保存static和transient修饰的变量的值,当然也不会保存访问修饰符(public, private, protected, 默认)信息。

序列化与继承关系

子类实现Serializable接口

根据Serializable规范【Java SE 8】

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

那么分两种情况:

1. 父类没有实现Serializable接口

那么序列化子类时,父类有、子类没有的属性不会被写到字节流中去,对应地反序列化时对这些属性不能从字节流中读取出来,只能(隐式)调用父类的无参构造器对这些属性初始化。这就要求序列化子类的未序列化父类必须要有无参构造器
父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Super {
public String publicField;
private String privateField;
protected String protectedField;
String packField;
public Super() {
this.publicField = "noArgPub";
this.privateField = "noArgPri";
this.protectedField = "noArgPro";
this.packField = "noArgPac";
}
public Super(String publicField, String privateField, String protectedField, String packField) {
this.publicField = publicField;
this.privateField = privateField;
this.protectedField = protectedField;
this.packField = packField;
}
public String getPrivateField() {
return privateField;
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
public class Sub extends Super implements Serializable {
private static final long serialVersionUID =1L;
public String subField;
public Sub(String superPublic, String superPrivate, String superProtected, String superPackage) {
super(superPublic, superPrivate, superProtected, superPackage);
}
}

序列化与反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class Serialize {
public static void main(String[] args) {
// 给父类进行初始化
Sub sub = new Sub("public", "private", "protected", "package");
sub.subField = "sub";
System.out.println("被序列化的对象");
System.out.println(sub.publicField);
System.out.println(sub.protectedField);
System.out.println(sub.getPrivateField());
System.out.println(sub.packField);
System.out.println(sub.subField);
//序列化
File file = new File("out.txt");
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(file);
oos = new ObjectOutputStream(fos);
oos.writeObject(sub);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
fos.close();
} catch (IOException e) {
System.out.println("关闭写入失败");
}
}
// 进行反序列化
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(file);
ois = new ObjectInputStream(fis);
try {
Sub sub1 = (Sub)ois.readObject();
System.out.println("反序列化生成的对象");
System.out.println(sub1.publicField);
System.out.println(sub1.protectedField);
System.out.println(sub1.getPrivateField());
System.out.println(sub1.packField);
System.out.println(sub1.subField);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ois.close();
fis.close();
} catch (IOException e) {
System.out.println("关闭读取失败");
}
}
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
被序列化的对象
public
protected
private
package
sub
反序列化生成的对象
noArgPub
noArgPro
noArgPri
noArgPac
sub

可以看到父类所有属性都是通过调用无参构造器初始化的。
注释掉子类和父类的无参构造器,再运行程序会出现下面异常。最终问题出在“Sub sub1 = (Sub)ois.readObject()”,反序列化的时候。

1
2
3
4
5
6
7
8
9
10
11
12
java.io.InvalidClassException: serialize.Sub; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at serialize.Serialize.main(Serialize.java:51)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

2. 父类实现了Serializable接口

使上述代码中的Super类实现Serializable接口,则结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
被序列化的对象
public
protected
private
package
sub
反序列化生成的对象
public
protected
private
package
sub

如果父类也实现了Serializable接口,那么序列化子类时会把父类的所有属性(包括私有属性)全都序列化,父类有没有无参构造器都没有关系。

父类实现Serializable接口,子类没有

根据Serializable规范【Java SE 8】

Serializability of a class is enabled by the class implementing the 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.

所有可序列化类的子类都是可序列化的,包括父类中没有的属性。
将上述代码中的子类和父类是否实现Serializable接口互换一下,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
被序列化的对象
public
protected
private
package
sub
反序列化生成的对象
public
protected
private
package
sub