java 抽象类是否应该有一个 serialVersionUID
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/893259/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Should an abstract class have a serialVersionUID
提问by Yishai
In java, if a class implements Serializable but is abstract, should it have a serialVersionUID long declared, or do the subclasses only require that?
在 java 中,如果一个类实现了 Serializable 但它是抽象的,它是否应该有一个很长的 serialVersionUID 声明,或者子类只需要它?
In this case it is indeed the intention that all the sub classes deal with serialization as the purpose of the type is to be used in RMI calls.
在这种情况下,实际上所有子类都处理序列化,因为该类型的目的是在 RMI 调用中使用。
采纳答案by Bill the Lizard
The serialVersionUID is provided to determine compatibility between a deseralized object and the current version of the class. As such, it isn't really necessary in the first version of a class, or in this case, in an abstract base class. You'll never have an instance of that abstract class to serialize/deserialize, so it doesn't need a serialVersionUID.
提供 serialVersionUID 以确定反序列化对象与类的当前版本之间的兼容性。 因此,在类的第一个版本中,或者在这种情况下,在抽象基类中,它并不是真正必要的。您永远不会有该抽象类的实例来序列化/反序列化,因此它不需要 serialVersionUID。
(Of course, it does generate a compiler warning, which you want to get rid of, right?)
(当然,它确实会生成编译器警告,您想摆脱它,对吗?)
It turns out james' comment is correct. The serialVersionUID of an abstract base class doesget propagated to subclasses. In light of that, you doneed the serialVersionUID in your base class.
事实证明詹姆斯的评论是正确的。抽象基类的 serialVersionUID确实会传播到子类。鉴于此,您确实需要基类中的 serialVersionUID。
The code to test:
要测试的代码:
import java.io.Serializable;
public abstract class Base implements Serializable {
private int x = 0;
private int y = 0;
private static final long serialVersionUID = 1L;
public String toString()
{
return "Base X: " + x + ", Base Y: " + y;
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Sub extends Base {
private int z = 0;
private static final long serialVersionUID = 1000L;
public String toString()
{
return super.toString() + ", Sub Z: " + z;
}
public static void main(String[] args)
{
Sub s1 = new Sub();
System.out.println( s1.toString() );
// Serialize the object and save it to a file
try {
FileOutputStream fout = new FileOutputStream("object.dat");
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject( s1 );
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
Sub s2 = null;
// Load the file and deserialize the object
try {
FileInputStream fin = new FileInputStream("object.dat");
ObjectInputStream ois = new ObjectInputStream(fin);
s2 = (Sub) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( s2.toString() );
}
}
Run the main in Sub once to get it to create and save an object. Then change the serialVersionUID in the Base class, comment out the lines in main that save the object (so it doesn't save it again, you just want to load the old one), and run it again. This will result in an exception
在 Sub 中运行一次 main 以使其创建和保存对象。然后更改Base类中的serialVersionUID,注释掉main中保存对象的行(这样它就不会再次保存它,你只想加载旧的),然后再次运行它。这将导致异常
java.io.InvalidClassException: Base; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
回答by Tom
Yes, in general, for the same reason that any other class needs a serial id - to avoid one being generated for it. Basically any class (not interface) that implements serializable should define serial version id or you risk de-serialization errors when the same .class compile is not in the server and client JVMs.
是的,一般来说,出于同样的原因,任何其他类都需要一个序列号 - 以避免为它生成一个序列号。基本上,任何实现可序列化的类(不是接口)都应该定义串行版本 ID,否则当相同的 .class 编译不在服务器和客户端 JVM 中时,您将面临反序列化错误的风险。
There are other options if you are trying to do something fancy. I'm not sure what you mean by "it is the intention of the sub classes...". Are you going to write custom serialization methods (eg. writeObject, readObject)? If so there are other options for dealing with a super class.
如果您想做一些奇特的事情,还有其他选择。我不确定您所说的“这是子类的意图......”是什么意思。您是否要编写自定义序列化方法(例如 writeObject、readObject)?如果是这样,还有其他处理超类的选择。
see: http://java.sun.com/javase/6/docs/api/java/io/Serializable.html
见:http: //java.sun.com/javase/6/docs/api/java/io/Serializable.html
HTH Tom
汤姆
回答by Viktor Stolbin
Actually, pointing out of Tom's link if missing serialVersionIDis actually calculated by serialization runtime i.e. not during compilation
实际上,如果缺失serialVersionID实际上是由序列化运行时计算的,即不是在编译期间,则指出 Tom 的链接
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...
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将根据类的各个方面为该类计算默认的 serialVersionUID 值...
This makes things even more complicated having different versions of JRE.
这使得具有不同版本的 JRE 的事情变得更加复杂。
回答by Oliv
Conceptually, the serialized data look like this:
从概念上讲,序列化数据如下所示:
subClassData(className + version + fieldNames + fieldValues)
parentClassData(className + version + fieldNames + fieldValues)
... (up to the first parent, that implements Serializable)
So when you deserialize, mismatch in version in any of the classes in the hierarchy causes the deserialization to fail. Nothing is stored for interfaces, so there's no need to specify version for them.
因此,当您反序列化时,层次结构中任何类中的版本不匹配都会导致反序列化失败。没有为接口存储任何内容,因此无需为它们指定版本。
Answer:yes, you do need to provide serialVersionUIDin the base abstract class as well. Even if it does not have fields (className + versionare stored even if there are no fields).
答:是的,您也需要serialVersionUID在基本抽象类中提供。即使它没有字段(className + version即使没有字段也会存储)。
Also note the following:
还要注意以下几点:
- If class does not have a field, that is found in the serialized data (a removed field), it is ignored.
- If class has a field, that is not present in serialized data (a new field), it's set to 0/false/null (not to default value as one would expect).
- if a field changes datatype, the deserialized value must be assignable to the new type. E.g. if you had an
Objectfield withStringvalue, changing the field type toStringwill succeed, but changing toIntegerwon't. However, changing field frominttolongwon't work, even though you can assignintvalue tolongvariable. - If subclass no longer extends the parent class, which it extends in the serialized data, it is ignored (as in case 1).
- If a subclass now extends a class, that is not found in serialized data, parent class fields are restored with 0/false/null value (as in case 2).
- 如果类没有在序列化数据中找到的字段(已删除的字段),则将其忽略。
- 如果类有一个字段,该字段不存在于序列化数据(新字段)中,则将其设置为 0/false/null(不是人们期望的默认值)。
- 如果字段更改数据类型,则反序列化值必须可分配给新类型。例如,如果您有一个
Object带String值的字段,String将字段类型更改为会成功,但更改为Integer不会。但是,即使您可以为变量赋值,将字段从 更改int为long也不起作用。intlong - 如果子类不再扩展它在序列化数据中扩展的父类,则忽略它(如情况 1)。
- 如果子类现在扩展了在序列化数据中找不到的类,则父类字段将恢复为 0/false/null 值(如情况 2)。
In simple words: you can reorder fields, add and remove them, even change the class hierarchy. You should not rename fields or classes (it won't fail, but the values won't be deserialized). You cannot change type of fields with primitive type, and you can change reference type fields provided the new type is assignable from all values.
简而言之:您可以对字段重新排序、添加和删除它们,甚至可以更改类层次结构。您不应重命名字段或类(它不会失败,但不会反序列化值)。您不能更改具有原始类型的字段类型,并且您可以更改引用类型字段,前提是新类型可从所有值分配。
Note: if the base class doesn't implement Serializable and only the subclass does, then fields from the base class would behave as transient.
注意:如果基类不实现 Serializable 而只有子类实现,则基类中的字段将表现为transient.

