What is the serialVersionUID? – 什么是serialVersionUID?

最后修改: 2017年 7月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The serialVersionUID attribute is an identifier that is used to serialize/deserialize an object of a Serializable class.

serialVersionUID属性是一个标识符,用于序列化/反序列化Serializable类的对象。

In this quick tutorial, we’ll discuss what is serialVersionUID and how to use it through examples.

在这个快速教程中,我们将讨论什么是serialVersionUID以及如何通过实例使用它。

2. Serial Version UID

2.序列版本UID

Simply put, we use the serialVersionUID attribute to remember versions of a Serializable class to verify that a loaded class and the serialized object are compatible.

简单地说,我们使用serialVersionUID属性来记忆Serializable类的版本,以验证加载的类和序列化的对象是否兼容。

The serialVersionUID attributes of different classes are independent. Therefore, it is not necessary for different classes to have unique values.

不同类的serialVersionUID属性是独立的。因此,不同的类没有必要有唯一的值。

Next, let’s learn how to use serialVersionUID through some examples.

接下来,让我们通过一些例子来学习如何使用serialVersionUID

Let’s start by creating a serializable class and declaring a serialVersionUID identifier:

让我们先创建一个可序列化的类并声明一个serialVersionUID标识符。

public class AppleProduct implements Serializable {

    private static final long serialVersionUID = 1234567L;

    public String headphonePort;
    public String thunderboltPort;
}

Next, we’ll need two utility classes: one to serialize an AppleProduct object into a String, and another to deserialize the object from that String:

接下来,我们需要两个实用类:一个用于将AppleProduct对象序列化为String,,另一个用于从String中反序列化该对象:

public class SerializationUtility {

    public static void main(String[] args) {
        AppleProduct macBook = new AppleProduct();
        macBook.headphonePort = "headphonePort2020";
        macBook.thunderboltPort = "thunderboltPort2020";

        String serializedObj = serializeObjectToString(macBook);
 
        System.out.println("Serialized AppleProduct object to string:");
        System.out.println(serializedObj);
    }

    public static String serializeObjectToString(Serializable o) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();
        
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }
}
public class DeserializationUtility {
 
    public static void main(String[] args) {
 
        String serializedObj = ... // ommited for clarity
        System.out.println(
          "Deserializing AppleProduct...");
 
        AppleProduct deserializedObj = (AppleProduct) deSerializeObjectFromString(
          serializedObj);
 
        System.out.println(
          "Headphone port of AppleProduct:"
            + deserializedObj.getHeadphonePort());
        System.out.println(
          "Thunderbolt port of AppleProduct:"
           + deserializedObj.getThunderboltPort());
    }
 
    public static Object deSerializeObjectFromString(String s)
      throws IOException, ClassNotFoundException {
  
        byte[] data = Base64.getDecoder().decode(s);
        ObjectInputStream ois = new ObjectInputStream(
          new ByteArrayInputStream(data));
        Object o = ois.readObject();
        ois.close();
        return o;
    }
}

We begin by running SerializationUtility.java, which saves (serializes) the AppleProduct object into a String instance, encoding the bytes using Base64.

我们首先运行SerializationUtility.java,它将AppleProduct对象保存(序列化)为String实例e,使用Base64对字节编码。

Then, using that String as an argument for the deserialization method, we run DeserializationUtility.java, which reassembles (deserializes) the AppleProduct object from the given String.

然后,使用该String作为反序列化方法的参数,我们运行DeserializationUtility.java,从给定的String.重新组装(反序列化)AppleProduct对象。

The output generated should be similar to this:

产生的输出应该与此类似。

Serialized AppleProduct object to string:
rO0ABXNyACljb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkFwcGxlUHJvZHVjdAAAAAAAEta
HAgADTAANaGVhZHBob25lUG9ydHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADmxpZ2h0ZW5pbmdQb3
J0cQB+AAFMAA90aHVuZGVyYm9sdFBvcnRxAH4AAXhwdAARaGVhZHBob25lUG9ydDIwMjBwdAATd
Gh1bmRlcmJvbHRQb3J0MjAyMA==
Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020

Now, let’s modify the serialVersionUID constant in AppleProduct.java, and reattempt to deserialize the AppleProduct object from the same String produced earlier. Re-running DeserializationUtility.java should generate this output.

现在,让我们修改serialVersionUID 常量在AppleProduct.java中,并重新尝试从先前产生的同一个String中反序列化AppleProduct对象。重新运行DeserializationUtility.java应该会产生这个输出。

Deserializing AppleProduct...
Exception in thread "main" java.io.InvalidClassException: com.baeldung.deserialization.AppleProduct; local class incompatible: stream classdesc serialVersionUID = 1234567, local class serialVersionUID = 7654321
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at com.baeldung.deserialization.DeserializationUtility.deSerializeObjectFromString(DeserializationUtility.java:24)
	at com.baeldung.deserialization.DeserializationUtility.main(DeserializationUtility.java:15)

By changing the serialVersionUID of the class, we modified its version/state. As a result, no compatible classes were found during deserialization, and an InvalidClassException was thrown.

通过改变类的serialVersionUID,我们修改了其版本/状态。结果,在反序列化过程中没有找到兼容的类,并且抛出了InvalidClassException

If serialVersionUID is not provided in a Serializable class, the JVM will generate one automatically. However, it is good practice to provide the serialVersionUID value and update it after changes to the class so that we can have control over the serialization/deserialization process. We’ll take a closer look at it in a later section.

如果serialVersionUID没有在Serializable类中提供,JVM将自动生成一个。然而,提供serialVersionUID值并在类发生变化后更新它是一个很好的做法,这样我们就可以控制序列化/反序列化过程。我们将在后面的章节中仔细研究它。

3. Compatible Changes

3.兼容的变化

Let’s say we need to add a new field lightningPort to our existing AppleProduct class:

假设我们需要向现有的AppleProduct类添加一个新字段lightningPort

public class AppleProduct implements Serializable {
//...
    public String lightningPort;
}

Since we are just adding a new field, no change in the serialVersionUID will be required. This is because, during the deserialization process, null will be assigned as the default value for the lightningPort field.

由于我们只是添加了一个新字段,不需要改变serialVersionUID。这是因为,在反序列化过程中,null将被分配为lightningPort字段的默认值

Let’s modify our DeserializationUtility class to print the value of this new field:

让我们修改我们的DeserializationUtility类来打印这个新字段的值。

System.out.println("LightningPort port of AppleProduct:"
  + deserializedObj.getLightningPort());

Now, when we rerun the DeserializationUtility class, we will see output similar to:

现在,当我们重新运行DeserializationUtility类时,我们将看到类似的输出。

Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020
Lightning port of AppleProduct:null

4. Default Serial Version

4.默认的串行版本

If we don’t define a serialVersionUID state for a Serializable class, then Java will define one based on some properties of the class itself such as the class name, instance fields, and so on.

如果我们不为Serializable 类定义一个serialVersionUID 状态,那么Java将根据类本身的一些属性(如类名、实例字段等)定义一个

Let’s define a simple Serializable class:

让我们定义一个简单的Serializable类。

public class DefaultSerial implements Serializable {
}

If we serialize an instance of this class like the following:

如果我们把这个类的一个实例像下面这样序列化。

DefaultSerial instance = new DefaultSerial();
System.out.println(SerializationUtility.serializeObjectToString(instance));

This will print the Base64 digest of the serialized binary:

这将打印序列化的二进制文件的Base64摘要。

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

Just like before, we should be able to deserialize this instance from the digest:

就像以前一样,我们应该能够从摘要中反序列化这个实例。

String digest = "rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpY" 
  + "WxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw";
DefaultSerial instance = (DefaultSerial) DeserializationUtility.deSerializeObjectFromString(digest);

However, some changes to this class may break the serialization compatibility. For instance, if we add a private field to this class:

然而,对这个类的一些改变可能会破坏序列化的兼容性。例如,如果我们给这个类添加一个private 字段。

public class DefaultSerial implements Serializable {
    private String name;
}

And then try to deserialize the same Base64 digest to a class instance, we’ll get an InvalidClassException:

然后尝试将相同的Base64摘要反序列化到一个类实例,我们会得到一个InvalidClassException:

Exception in thread "main" java.io.InvalidClassException: 
  com.baeldung.deserialization.DefaultSerial; local class incompatible: 
  stream classdesc serialVersionUID = 9045863543269746292, 
  local class serialVersionUID = -2692722436255640434

Because of this sort of unwanted incompatibility, it’s always a good idea to declare a serialVersionUID in Serializable classes. This way we can keep or evolve the version as the class itself evolves.

由于这种不必要的不兼容,在Serializable类中声明一个serialVersionUID总是一个好主意。这样我们就可以随着类本身的发展而保持或发展其版本。

5. Conclusion

5.结论

In this quick article, we demonstrated the use of the serialVersionUID constant to facilitate versioning of serialized data.

在这篇快速文章中,我们演示了使用serialVersionUID常量来促进序列化数据的版本管理。

As always, the code samples used throughout this article can be found over on GitHub.

一如既往,本文中所使用的代码样本可以在GitHub上找到