Java:简述对Serializable序列化的认识

一般情况下,我们在定义实体类时会继承Serializable接口,还有一个serialVersionUID变量。如下所示,那么他们有什么用处呢?

public class User implements Serializable {
	private static final long serialVersionUID = 1L;

	......
}

一、Serializable接口

Serializable接口是一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。

1.1 什么是序列化?

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

把对象转换为字节序列的过程称为对象的序列化;把字节序列恢复为对象的过程称为对象的反序列化。

1.2 为什么要序列化对象?

当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化。

1.3 为什么序列化要继承Serializable?

存储对象在存储介质中,为了方便在下次使用的时候,可以很快捷的重建一个副本。

我们来看看Serializable接口源码,发现竟然什么都没有,只是个空接口。

public interface Serializable {
}

一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。

在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。

Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。

二、serialversionUID变量

serialVersionUID::字面意思上是序列化的版本号,这个版本号确保了不同版本之间的兼容性,不仅能够向前兼容,还能够向后兼容,即在版本升级时反序列化仍保持对象的唯一性。

它有两种生成方式:

一个是默认的1L,比如:
private static final long serialVersionUID = 1L;

一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:  
private static final long serialVersionUID = xxxxL;

简单看一下 Serializable接口的说明,从说明中可以看到,如果我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID。

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification.  

However, it is <em>strongly recommended</em> that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected<code>InvalidClassException</code>s during deserialization.

如果没有显式的声明序列号,在程序编译时候会自动生成一个序列号,存储在文件中,但是在更改了实体类的时候又会重新生成一个序列号,在程序运行的时候,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常【InvalidClassException】。

如果显式的声明了这个序列号,不论如何修改类中的成员变量还是方法,都不会引起版本之间不兼容得问题,加强了程序的健壮性。所以强烈建议用户自定义一个serialVersionUID。

在文章开始我们已经新建了一个实体类User实现Serializable接口,并且定义了serialVersionUID变量。我们把User写到文件,然后读取出来。

	public static void writeObject(String path) {
		User user = new User();
		user.setUserName("张三");

		try {
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(path)));
			objectOutputStream.writeObject(user);
			objectOutputStream.close();

			System.out.println("对象序列化成功");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public static void readeObject(String path) {
		ObjectInputStream objectOutputStream;
		try {
			objectOutputStream = new ObjectInputStream(new FileInputStream(new File(path)));
			User user = (User) objectOutputStream.readObject();

			objectOutputStream.close();

			System.out.println("对象反序列化成功");
			System.out.println("UserName:" + user.getUserName());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

输出如下:

 sr com.se.User        L userNamet Ljava/lang/String;xpt 寮犱笁

对象序列化成功
对象反序列化成功
UserName:张三

序列化与反序列化操作过程就是这么的简单。只需要将User写入到文件中,然后再从文件中进行恢复,恢复后得到的内容与之前完全一样,但是两者是不同的对象。

如果将serialVersionUID变量去掉,我们来看看,会发生什么事情。

java.io.NotSerializableException: com.se.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.se.SeTest.writeObject(SeTest.java:26)
	at com.se.SeTest.main(SeTest.java:16)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.se.User
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at com.se.SeTest.readeObject(SeTest.java:42)
	at com.se.SeTest.main(SeTest.java:17)
Caused by: java.io.NotSerializableException: com.se.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.se.SeTest.writeObject(SeTest.java:26)
	at com.se.SeTest.main(SeTest.java:16)

serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。

serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误:

java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -1451587475819212328, local class serialVersionUID = -3946714849072033140
	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 Main.readUser(Main.java:32)
	at Main.main(Main.java:10)
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页