zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java里Serializable的那些事

JAVA 那些 serializable
2023-09-27 14:27:47 时间

通过java的ObjectOutputStream、ObjectInputStream类能对实现了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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import  java.io.Serializable;
 
import  com.alibaba.fastjson.JSON;
 
public  class  Person  implements  Serializable{
     private  static  final  long  serialVersionUID = 1L;
     
     private  int  id;
     private  String name;
 
     public  Person() {
         super ();
     }
 
     public  Person( int  id, String name) {
         super ();
         this .id = id;
         this .name = name;
     }
     
     public  int  getId() {
         return  id;
     }
     public  void  setId( int  id) {
         this .id = id;
     }
     public  String getName() {
         return  name;
     }
     public  void  setName(String name) {
         this .name = name;
     }
 
     @Override
     public  String toString() {<br>           // 这里偷懒用了Fastjson
         return  JSON.toJSONString( this );
     }
}

   

序列化(写入对象):

反序列化(读取对象)

 

通过这样就能把一个对象存入磁盘里,并且能读取到内存里

 

以上是Serializable的基本用法,接下来介绍一些使用中的小细节(关于以下出现的术语,保存对象与序列化,读取对象与反序列化都分别是同一个意思)。

1,将对象序列化到本地后,对类进行修改,增加或减少字段(成员变量)或交换字段位置,然后反序列化之前保存的对象,都不会报错。

例如去掉id字段并增加newValue字段:

此时能正常读取之前序列化的对象(newValue字段因为值是null,这里默认不展示):

 

2,改变serialVersionUID值后,读取时发生异常

 java.io.InvalidClassException: com.ye.test.model.Movie;  Incompatible class (SUID): com.ye.test.model.Movie: static final long serialVersionUID =1L; but expected com.ye.test.model.Movie: static final long serialVersionUID =2L;
因为serialVersionUID值改变后,类定义的版本变化了,所以读取老版本对象将可能不兼容新对象,因此系统抛出异常
读取为null:
 
3,保存的时候对象有serialVersionUID,然后删除这个字段,再次读取
则读取时serialVersionUID会被赋值一个自动生成的值
这个值根据类的成员变量方法定义等算出,增减成员变量或者增减方法都会导致这个值的改变
但是如果增加空格,交换成员变量位置等都不会导致这个值被改变
java.io.InvalidClassException: com.ye.test.model.Movie; Incompatible class (SUID): com.ye.test.model.Movie: static final long serialVersionUID =1L; but expected com.ye.test.model.Movie: static final long serialVersionUID =- 6926675519878447654L;
也就是说,如果我们的Person类没有声明serialVersionUID变量,则以后这个类只要涉及到增减成员变量或者增减方法都会导致读取对象时不兼容。当然方法位置的调整不会影响。
 
4,如果先序列化保存一个对象到磁盘,然后修改这个类的定义,将其继承至一个基类(基类没有实现Serializable接口),则会报错
 java.io.InvalidClassException: com.ye.test.model.BaseMovie;  IllegalAccessException
at java.io.ObjectStreamClass. resolveConstructorClass(ObjectStreamClass.java:692)
如果基类实现了Serializable接口,则能正常读取
 
5,如果基类已经实现了Serializable接口,则子类无需再次实现(当然子类再次实现也没关系)
 
6,父类的serialVersionUID成员变量对子类没有影响,即使他是public的,所以反序列化时能不能兼容只会跟子类的serialVersionUID对象数值相关。
也就是说开发中如果我们定义的类已经修改,需要告诉系统我们的类已经不再兼容老版本,则需要修改子类的serialVersionUID版本号而不是父类。
 
7,如果子类实现Serializable接口,父类没有实现,则只可以执行序列化,不能执行反序列化,执行反序列化时报错信息同4
 
总结:
1,如果存在继承关系,则只需在基类实现Serializable接口即可
2,serialVersionUID变量需在子类定义,父类定义没有意义(父类定义serialVersionUID用来消除编译警告信息也没关系)
3,如果没有定义serialVersionUID变量,则系统在保存或读取时会根据类结构自动算出一个值赋予serialVersionUID,如果类结构没有变化,那么这个值则是固定的,所以还是尽量使用自定义的值比较便于维护。
4,如果需求变更导致类结构调整,那么如果需要继续读取老版本对象的话, 则不要修改serialVersionUID的版本号,如果需要使用全新的数据而抛弃之前保存在磁盘里的对象,则修改serialVersionUID版本号,系统会默认读取为null。相当于之前没有保存过。
 
最后附上demo,有兴趣的话可以自己测试下。