Java学习札记11:What is serialVersionUID?

本文通过实例解释了serialVersionUID的作用,展示了当serialVersionUID值不匹配时如何引发InvalidClassException异常,并讨论了为什么建议开发者自定义serialVersionUID。

Most people learn about serialVersionUID after they write their first serializable object (I know I did). You add ‘implements Serializable’ and in the next moment your IDE starts complaining… so what’s up?


Lets look at a simple example to see what meaning that variable has. In the example we will use the class SerializeMe shown below:

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

	private int data;

	public SerializeMe (int data) {
		this.data = data;
	}

	public int getData() {
		return data;
	}
}

Field data represents some information stored in the class. Class implements the Serializable interface, so my IDE automatically offered me to declare the serialVersionUID field. Lets start with value 1 set there.


We will also need some class that will serialize and deserialize the SerializeMe class. Here it is:

public class UIDTester {
	public static void main(String... strings) throws Exception {
		File file = new File("out.ser");
		FileOutputStream fos = new FileOutputStream(file);
		ObjectOutputStream oos = new ObjectOutputStream(fos);

		SerializeMe serializeMe = new SerializeMe(1);
		oos.writeObject(serializeMe);
		oos.close();

		FileInputStream fis = new FileInputStream(file);
		ObjectInputStream ois = new ObjectInputStream(fis);

		SerializeMe dto = (SerializeMe) ois.readObject();
		System.out.println("data : " + dto.getData());
		ois.close();
	}
}

This code will serialize an instance of class SerializeMe to a file and then deserialize it back. Now let’s run the main function! You should get output that says:

data : 1

It means that our object was properly serialized and deserialized. Note that the file “out.ser” was created on disk and it is still there even after the program finished. Let’s see if we can read that file once again, this time without creating it first. To do that, comment out lines from 4 to 9 in the UIDTester class:

public class UIDTester {
	public static void main(String... strings) throws Exception {
		File file = new File("out.ser");
		//FileOutputStream fos = new FileOutputStream(file);
		//ObjectOutputStream oos = new ObjectOutputStream(fos);

		//SerializeMe serializeMe = new SerializeMe(1);
		//oos.writeObject(serializeMe);
		//oos.close();

		FileInputStream fis = new FileInputStream(file);
		ObjectInputStream ois = new ObjectInputStream(fis);

		SerializeMe dto = (SerializeMe) ois.readObject();
		System.out.println("data : " + dto.getData());
		ois.close();
	}
}

This way, the main method starts right away with reading the file from the disk and deserializing data stored in it. As the file is exactly the same and our class didn’t changed either, we should get exactly the same output:

data : 1


Now, let’s see what happens, when we change the serialVersionUID value and try to deserialize once again our file. Change the line 2 in the class SerializeMe so that serialVersionUID contains now 2 instead of 1:

private static final long serialVersionUID = 2L;

Now let’s run again our program (just like one step before, with commented out lines writing the file). We should get an exception like this:

Exception in thread "main" java.io.InvalidClassException:
SerializeMe; local class incompatible: stream classdesc
serialVersionUID = 1, local class serialVersionUID = 2


As you can see, this time the deserialization didn’t go well. ObjectInputStream complained about the serialVersionUID being changed. How does he know that it changed? If serialVersinUID is static, then it should not have been serialized in the first place, and there should be no information about the previous value 1 during the deserialization, right? Well, serialVersionUID is an exception to the rule that “static fields don’t get serialized”. ObjectOutputStream writes every time the value of serialVersionUID to the output stream. ObjectInputStream reads it back and if the value read from the stream does not agree with the serialVersionUID value in the current version of the class, then it throws the InvalidClassException. Moreover, if there is no serialVersionUID officially declared in the class to be serialized, compiler automatically adds it with a value generated based on the fields declared in the class.


So what is it for after all? Let’s suppose that there is some file storing a serialized object of some class A. The deserialization of that object does not necessarily have to occur exactly after serialization. It can occur after a few months or on a completely different JVM (i.e. sending an object through net using serialization). In both cases, there is a chance that the class declaration has changed between serialization and deserialization. It would be nice to have some kind of versioning system for every serializable class – and serialVersionUID does exactly that. It checks if the data read from the input stream is compatible with the current definition of the class.


If so, then why is it recommended to specify your own serialVersionUID ? It gives us simply more control. Default rules for generating serialVersionUID can be too strict in some cases. For example when the visibility of a field changes, the serialVersionUID changes too. Value generated automatically can differ between various Java implementations. There is a chance that some object serialized on one Java implementation will not deserialize on some other Java implementation, even if the class definition is exactly the same. Furthermore, sometimes you just want for some reason to forbid deserialization of old serialized objects, and in this case you just have to change the serialVersionUID.


Now that you know that specifying your own serialVersionUID is recommended, you might tend to write it once for every serializable class ( or have it generated by the IDE ) and forget about it. WRONG !!! If you write it once and don’t take care to update it when necessary, you loose all the merits of serialVersionUID. All your classes will get deserialized without exceptions even when using outdated data. What you should do is to change serialVersionUID (for example increase it by 1 or make your IDE generate automatically a new value) every time there is some change in the definition of data stored in the class. For example if you change data types, variable names or add new data – hence every time you want to have ‘backward incompatibility’ for deserialization.



转载自:

http://www.javablogging.com/what-is-serialversionuid/










### 三级标题:Java反序列化时serialVersionUID不一致的解决方法 在Java中,当序列化和反序列化过程中类的`serialVersionUID`不一致时,会抛出`InvalidClassException`异常,提示“local class incompatible: stream classdesc serialVersionUID = XXXX, local class serialVersionUID = XXXX”。此异常通常发生在以下几种情况: 1. **类的结构发生变化**:例如添加、删除或修改了类的字段。 2. **类的实现发生了变化**:例如类实现了新的接口或不再实现某个接口。 3. **类的访问权限发生变化**:例如类从`public`变为`private`。 4. **类的`serialVersionUID`显式声明后未保持一致**。 解决此问题的方法包括: #### 显式声明`serialVersionUID` 在类中显式声明`serialVersionUID`字段,以确保即使类的结构发生变化,反序列化过程仍然可以正常进行。这可以通过以下方式实现: ```java private static final long serialVersionUID = 1L; ``` #### 使用`serialver`工具生成`serialVersionUID` Java提供了`serialver`工具来帮助生成`serialVersionUID`。这个工具可以根据类的当前状态生成一个唯一的版本号。使用方法如下: ```bash serialver -classpath <class_path> <fully_qualified_class_name> ``` #### 保持类的兼容性 如果类的结构发生了变化,但希望保持与旧版本的兼容性,可以采取以下措施: - **避免删除或重命名字段**:如果必须删除或重命名字段,可以提供一个兼容的替代方案。 - **使用`@Serial`注解**:在Java 14及以上版本中,可以使用`@Serial`注解来标记序列化相关的字段和方法,以提高代码的可读性和维护性。 #### 使用版本控制 在开发过程中,可以使用版本控制系统(如Git)来跟踪类的变化,并确保每次提交时都检查`serialVersionUID`是否需要更新。 #### 忽略版本不一致 在某些情况下,可能希望忽略版本不一致的问题。这可以通过自定义`ObjectInputStream`子类并重写`readClassDescriptor`方法来实现。然而,这种方法风险较高,可能导致数据丢失或不一致。 ### 示例代码 以下是一个简单的示例,展示了如何显式声明`serialVersionUID`: ```java import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // Getters and setters } ``` ### 相关问题 1. 如何在Java中生成`serialVersionUID`? 2. `serialVersionUID`的作用是什么? 3. 如果不显式声明`serialVersionUID`会发生什么? 4. 如何在不破坏兼容性的前提下修改可序列化的类? 5. `InvalidClassException`异常的常见原因有哪些?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值