Fork me on GitHub

Java序列化—Serializable

1. 序列化基本原理

  • 序列化:对象—> IO
  • 反序列化:IO —>对象
  • 意义:序列化将 Java 对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输(例如 rpc )再反序列之后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
  • 使用原理:序列化一个对象,先创建 OutputStream (如 FileOutputStream ),然后将 OutputStream 封装在ObjectOutputStream 中,最后调用 writeObject 方法序列化;反序列化需要将 InputStream (如FileInputstream )封装在 ObjectInputStream 内,最后调用 readObject 方法。

如果想要一个类可序列化,只要实现 Serializable 接口即可。

2. 变量是其他引用类

如果我们要序列化的类的某个变量是其他类,那么作为变量的那个类也必须序列化。

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
public class User implements Serializable {

private static final long serialVersionUID = -1115699428251281471L;

private String name;

private static String mobile;

private transient String password;

private String address;

private AppUser appUser;

}

public class AppUser{

private String email;

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}

代码中省略了get set 等其他方法。单元测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void userWriteTest() throws Exception {
User user = new User();
user.setName("zhaoxiaofa");
user.setMobile("13812345678");
user.setPassword("123456");
AppUser appUser = new AppUser();
appUser.setEmail("123@qq.com");
user.setAppUser(appUser);
System.out.println("写入磁盘的对象:" + user.toString());
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("/user.txt"));
o.writeObject(user);
o.close();
}

执行结果为:

java.io.NotSerializableException:

因为 AppUser 不可序列化,导致 User 也无法序列化。

3. serialVersionUID

在实体类实现 Serializable 接口之后,一般我们会指定 serialVersionUID 。如果不指定会有问题吗?我们来试一下:

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
public class User implements Serializable {

private String name;

private static String mobile;

private transient String password;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMobile() {
return mobile;
}

public void setMobile(String mobile) {
this.mobile = mobile;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", mobile='" + mobile + '\'' +
'}';
}

}

执行序列化测试:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void userWriteTest() throws Exception {
User user = new User();
user.setName("zhaoxiaofa");
user.setMobile("13812345678");
user.setPassword("123456");
System.out.println("写入磁盘的对象:" + user.toString());
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("/user.txt"));
o.writeObject(user);
o.close();
}

执行成功,如果我们现在有需求修改 User 类,新加一个变量,如下:

1
private String address;

对应添加 get 、set 方法,然后执行反序列化单元测试:

1
2
3
4
5
6
7
@Test
public void userReadTest() throws Exception{
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/user.txt"));
User object = (User) in.readObject();
System.out.println("读取磁盘的对象:" + object.toString());
in.close();
}

执行结果为:

java.io.InvalidClassException: local class incompatible: stream classdesc serialVersionUID = 3391156310830289756, local class serialVersionUID = 8094924620444168806

由报错信息可知,虽然我们没有设置 serialVersionUID 的值,但是序列化的时候默认生成了。具体的生成方法我没有研究过,但肯定和类的变量有关。那么,当类的变量发生变化时,序列化功能就会出现问题。我们需要保证它的兼容性。建议每个序列化的类都指定 serialVersionUID。

阿里巴巴 Java 开发手册有如下强制规定:

10.【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如 果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。

4. 同一个对象多次序列化

同一个对象会被序列化多次吗?

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
public class AppUser implements Serializable {

private static final long serialVersionUID = -1014920790204013525L;

private String email;

// 省略 get set 方法
}

public class User implements Serializable {

private static final long serialVersionUID = -1115699428251281471L;

private String name;

private static String mobile;

private transient String password;

private String address;

private AppUser appUser;

// 省略 get set 方法
}

单元测试如下:

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
@Test
public void userWriteTest() throws Exception {
AppUser appUser = new AppUser();
appUser.setEmail("123@qq.com");

User user1 = new User();
user1.setName("zhaoxiaofa");
user1.setMobile("13812345678");
user1.setPassword("123456");
user1.setAppUser(appUser);

User user2 = new User();
user2.setName("zhangsan");
user2.setMobile("13212345678");
user2.setPassword("654321");
user2.setAppUser(appUser);

ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("/user.txt"));
o.writeObject(appUser);
o.writeObject(user1);
o.writeObject(user2);
o.writeObject(user1);
o.close();
}

@Test
public void userReadTest() throws Exception{
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/user.txt"));
AppUser appUser = (AppUser) in.readObject();
User user1 = (User) in.readObject();
User user2 = (User) in.readObject();
User user11 = (User) in.readObject();
System.out.println(user1 == user2);
System.out.println(user1.getAppUser() == appUser);
System.out.println(user2.getAppUser() == appUser);
System.out.println(user1.getAppUser() == user11.getAppUser());
in.close();
}

分别序列化 appUser、user1、user2,其中 user1 序列化了两次,然后按照顺序反序列化。执行结果如下:

false
true
true
true

说明,appUser 从始至终只被序列化了一次,user1 和 user2 中的 appUser 是同一个对象。user1 和 user11 也是反序列化的同一个对象。

结论:同一个对象只会被序列化一次。

本文标题:Java序列化—Serializable

原始链接:https://zhaoxiaofa.com/2019/01/05/Java序列化—Serializable/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。