Serializable接口的作用
Serializable接口的作用
1、什么是序列化与反序列化?
序列化: 1、序列化就是将对象属性转变为二进制数据。2、在网络上进行传输。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。
2、Serializable 简介
实现java.io.Serializable 接口的类是可序列化的,这个序列化接口没有任何方法和域,仅用于标识序列化的语意。 没有实现此接口的类将不能使它们的任意状态被序列化或逆序列化。这个接口其实是个空接口,那么这个序列化操作,到底是由谁去实现了呢?其实,看一下接口的注释说明就知道,当我们让实体类实现Serializable接口时,其实是在告诉JVM此类可被序列化,可被默认的序列化机制序列化。
public interface Serializable { }
3、Serializable 序列化测试
3.1、新建序列化测试类
/**
* @Author dw
* @ClassName SerializableTest
* @Description
* @Date 2023/1/2 15:08
* @Version 1.0
*/
public class SerializableTest {
public static void main(String[] args) {
// 初始化
User user = new User();
user.setName("王二");
user.setAge(18);
System.out.println(user);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.txt"))){
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("user.txt")))){
User userRead = (User) ois.readObject();
System.out.println(userRead);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}
由于 User没有实现 Serializable 接口,所以系统会报错。运行后报错如下:
顺着堆栈信息,我们来看一下 ObjectOutputStream 的 writeObject0 () 方法。其部分源码如下:
这段代码的意思是,ObjectOutPutStream 在序列化的时候,会判断对象的类型,如果不是字符串、数组、枚举、Serializable 的实例,会抛出 NotSerializableException。但是,如果 SClass 实现了 Serializable 接口的话,就可以被序列化和反序列化了。
3.2、修改User类实现Serialiable接口。
public static class User implements Serializable{
private static final long serialVersionUID = -9085952353374185653L;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
再次测试, 序列化成功。
3.3、反序列化测试:修改User类中的序列化id, 测试能否序列化成功
public static class User implements Serializable{
private static final long serialVersionUID = -9085952353374185654L;
...
}
提示如下错误:
com.dw.study.test.SerializableTest$User@5f184fc6
java.io.InvalidClassException: com.dw.study.test.SerializableTest$User;
local class incompatible: stream classdesc serialVersionUID = -9085952353374185654, local class serialVersionUID = -9085952353374185655
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at com.dw.study.test.SerializableTest.main(SerializableTest.java:31)
serialVersionUID
被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID
与被序列化类中的 serialVersionUID
进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。// 序列化前的对象名称
public static class User implements Serializable{}
// 序列化后的对象名称
public static class MyUser implements Serializable{}
反序列化结果:
提示User类未找到。
4、serialversionuid 默认1L和64位的哈希字段有什么区别?
2.有两种生成方式:
一个是默认的1L,比如:private static final long serialVersionUID = 1L;
一个是根据类名等来生成一个64位的哈希字段
由此可以证明,Java 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还有一个非常重要的因素就是序列化 ID 是否一致。
也就是说,如果没有特殊需求,采用默认的序列化 ID(1L)就可以,这样可以确保代码一致时反序列化成功。
6、总结
1. static 和 transient 修饰的字段是不会被序列化的。因为序列化保存的是对象的状态,而 static 修饰的字段属于类的状态,因此可以证明序列化并不保存 static 修饰的字段。
transient 的中文字义为 “临时的”(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null。
2. serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。
Java大部分的数据类型都已经实现了可序列化接口。