我们在平时的开发过程中,经常发现很多实体类都实现了serializable这个接口,有些又没有实现,好像实现与否都没有太大差别,只知道serializable这个接口是实现序列化的,那么什么是序列化呢?有什么作用呢?这些都不是很清楚。
什么是序列化
把对象转化成字节序列的过程称为对象的序列化。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。(因为静态static的东西在方法区)。
在程序中为了能直接以 Java 对象的形式进行保存,然后再重新得到该 Java 对象,这就需要序列化能力。序列化其实可以看成是一种机制,按照一定的格式将 Java 对象的某状态转成介质可接受的形式,以方便存储或传输。其实想想就大致清楚基本流程,序列化时将 Java 对象相关的类信息、属性及属性值等等保存起来,反序列化时再根据这些信息构建出 Java 对象。而过程可能涉及到其他对象的引用,所以这里引用的对象的相关信息也要参与序列化。
序列化的作用
1.持久化存储。可以将对象持久化到介质中,就像实现对象直接存储。
2.对于远程调用,能方便对对象进行编码和解码,就像实现对象直接传输。
3.提供一种简单又可扩展的对象保存恢复机制。
4.允许对象自定义外部存储的格式
实现方式
Java 中进行序列化操作需要实现 Serializable 接口。该接口是一个空接口,起到标识的作用。当我们让实体类实现Serializable接口时,其实是在告诉JVM此类可被序列化,可被默认的序列化机制序列化。
何时需要序列化
在存储时需要序列化,这是肯定的。大家知道的是序列化是将对象进行流化存储,我们有时候感觉自己在项目中并没有进行序列化操作,也一样是存进去了,那么对象需要经过序列化才能存储的说法,似乎从这儿就给阉割了。事实究竟是怎样的呢?
首先看我们常用的数据类型类声明:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
public class Date implements java.io.Serializable, Cloneable, Comparable
而像其他int、long、boolean类型等,都是基本数据类型,数据库里面有与之对应的数据结构。从上面的类声明来看,我们以为的没有进行序列化,其实是在声明的各个不同变量的时候,由具体的数据类型帮助我们实现了序列化操作。
那么,既然实体类的变量都已经帮助我们实现了序列化,为什么我们仍然要显示的让类实现serializable接口呢?
首先,序列化的目的有两个,第一个是便于存储,第二个是便于传输。我们一般的实体类不需要程序员再次实现序列化的时候,请想两个问题:第一:存储媒体里面,是否是有其相对应的数据结构?第二:这个实体类,是否需要远程传输(或者两个不同系统甚至是分布式模块之间的调用)?
如果说我们需要将实体类信息写入一个txt文档中,那么不实现序列化接口,就会出错。
实现序列化前:
//实体类
public class Father {
public int f;
}
//测试类
public class SerializeTest {
public static void main(String[] args) throws Exception{
Father father = new Father();
father.setF(5);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/pig.txt")));
oos.writeObject(father);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/pig.txt")));
Father f = (Father) ois.readObject();
System.out.println("Father对象反序列化:" + f.toString());
}
}
测试结果:
Exception in thread "main" java.io.NotSerializableException: com.redistext.serialize.Father
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.redistext.serialize.SerializeTest.main(SerializeTest.java:18)
实现序列化后:
//实体类
public class Father implements Serializable {
private static final long serialVersionUID = 3569247533577774553L;
public int f;
}
//测试类
public class SerializeTest {
public static void main(String[] args) throws Exception{
Father father = new Father();
father.setF(5);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/pig.txt")));
oos.writeObject(father);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/pig.txt")));
Father f = (Father) ois.readObject();
System.out.println("Father对象反序列化:" + f.toString());
}
}
测试结果:
Father对象反序列化:Father(f=5)
遇到的问题
transient修饰的属性,不会被序列化。
静态static的属性,不会被序列化。
实现Serializable接口的时候,一定要给SerialVersionUID赋值。
当属性是对象的时候,对象也要实现序列化接口。
代码测试
实体类:
package com.redistext.serialize;
import lombok.Data;
import java.io.Serializable;
/**
* @author: maorui
* @description: TODO
* @date: 2021/10/26 20:09
* @description: V1.0
*/
@Data
public class FlyPig implements Serializable{
// private static final long serialVersionUID = 532067060492947515L;
private static String age = "25";
private String name;
private String color;
transient private String car;
// private String addTip;
}
测试类:
package com.redistext.serialize;
import java.io.*;
/**
* @author: maorui
* @description: TODO
* @date: 2021/10/26 20:08
* @description: V1.0
*/
public class SerializeTest {
public static void main(String[] args) throws Exception{
serializeFlyPig();
FlyPig flyPig = deserializeFlyPig();
System.out.println(flyPig.toString());
}
private static void serializeFlyPig() throws Exception{
FlyPig flyPig = new FlyPig();
flyPig.setCar("audi");
flyPig.setColor("red");
flyPig.setName("aaaa");
//ObjectOutputStream 对象输出流,将flyPig对象存储在D盘的pig.txt文件中,完成对flyPig对象的序列化操作
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/pig.txt")));
System.out.println("序列化前flyPig对象信息:" + flyPig.toString());
oos.writeObject(flyPig);
System.out.println("FlyPig 对象序列化成功!");
oos.close();
}
private static FlyPig deserializeFlyPig() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/pig.txt")));
FlyPig flyPig = (FlyPig) ois.readObject();
System.out.println("FlyPig 对象反序列化成功!");
return flyPig;
}
}
测试结果:
根据测试结果可以发现transient关键字修饰的属性无法被序列化。
上面的FlgPig实体类中,我们把SerialVersionUID的赋值语句给注释掉了,好像也能实现序列化,那有无SerialVersionUID的赋值语句有什么作用呢?
我们可以这样测试一下:
1. 放开FlgPig实体类的addTip字段的赋值语句。
2.测试类不进行序列化操作,直接进行反序列化操作。
测试结果:
Exception in thread "main" java.io.InvalidClassException: com.redistext.serialize.FlyPig; local class incompatible: stream classdesc serialVersionUID = -8106182154492238666, local class serialVersionUID = -1124294899602738656
发现开始时反序列化的FlgPig实体类的serialVersionUID和加了addTip字段的FlgPig实体类的serialVersionUID不一致,无法进行赋值。
如果在最开始就加上serialVersionUID值,就不会出现这个情况。因为虽然加了addTip字段,但是serialVersionUID还是同一个,就可以进行反序列化。
SerializableID号是根据类的特征和类的签名算出来的.为什么ID号那么长,是因为为了避免重复.所以Serializable是给类加上id用的. 用于判断类和对象是否是同一个版本。
序列化的时候系统会把当前类的serialVersionUID(没有显式赋值系统会默认生成一个) 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID ,看它是否和当前类的serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候就会抛异常,反序列化失败。
serialVersionUID作用
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 声明serialVersionUID ,可以很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量,也可能增加了一些新的成员变量,这个时候我们的反序列化依然能够成功,程序依然能够最大程度地恢复数据,相反,如果不指定serialVersionUID ,程序就会挂掉。