什么是serialVersionUID,为什么要使用它?


Answers:


2289

java.io.Serializable您可能会得到的有关文档的解释很好:

序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。如果接收者为对象加载的类serialVersionUID与相应的发送者的类不同,则反序列化将导致 InvalidClassException。可序列化的类可以serialVersionUID通过声明一个serialVersionUID必须为static,final和type 的字段来显式声明其自身long

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可序列化的类未显式声明a serialVersionUID,则序列化运行时将根据serialVersionUID该类的各个方面为该类计算默认值,如Java™对象序列化规范中所述。但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的情况下serialVersionUID计算对类详细信息高度敏感,而类详细信息可能会根据编译器的实现而有所不同,因此可能导致InvalidClassExceptions反序列化期间发生意外情况。因此,为了保证serialVersionUID不同Java编译器实现之间的值一致,可序列化的类必须声明一个显式serialVersionUID值。还强烈建议明确serialVersionUID声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类serialVersionUID字段,不能用作继承成员。


325
因此,您实际上要说的是,如果用户不了解上述所有内容,那么该用户就不用担心担心序列化了吗?我相信您回答了“如何?” 而不是解释“为什么?”。我,其中一个,我不明白为什么我对SerializableVersionUID感到困扰。
Ziggy

365
原因在第二段中:如果您未明确指定serialVersionUID,则会自动生成一个值-但这很脆弱,因为它取决于编译器的实现。
乔恩·斯基特

14
为什么Eclipse会说我需要“私有静态最终长serialVersionUID = 1L”?当我扩展Exception类时?
JohnMerlino 2014年

21
@JohnMerlino:好吧,我不希望它说您需要一个-但它可能是一个建议,以帮助您正确地序列化异常。如果您不打算序列化它们,则实际上不需要常量。
乔恩·斯基特

16
@JohnMerlino,回答您的问题部分原因:异常实现了Serializable并且eclipse警告您尚未设置serialVersionUID,这是一个好主意(如果您不想序列化该类),以避免JonSkeet发表的问题轮廓。
zpon 2014年

475

如果您只是为了实现的需要而进行序列化(例如,谁在乎是否为序列化HTTPSession……如果它是否已存储,那么您可能不在乎de-serializing表单对象),就可以进行序列化。忽略这个。

如果您实际上正在使用序列化,则仅当您计划直接使用序列化来存储和检索对象时才重要。该serialVersionUID代表你的类版本,你应该增加,如果你的类的当前版本不向后与以前的版本兼容。

大多数时候,您可能不会直接使用序列化。在这种情况下,请SerialVersionUID通过单击快速修复选项生成默认值,不必担心。


78
我想说的是,如果您不使用序列化来永久存储,则应使用@SuppressWarnings而不是添加值。它减少了类的混乱,并且保留了serialVersionUID机制的功能,以防止发生不兼容的更改。
汤姆·安德森

25
我看不到如何添加一行(@SuppressWarnings注释)而不是另一行(可序列化的ID)会“使类变得更少”。而且,如果您不使用序列化来永久存储,为什么不只使用“ 1”呢?无论如何,您都不在乎自动生成的ID。
MetroidFan2002 2011年

71
@ MetroidFan2002:我认为@TomAnderson serialVersionUID保护您免受不兼容更改的影响是正确的。@SuppressWarnings如果您不希望将该类用于永久存储,则使用文档的意图会更好。
AbdullahC

11
“如果您的类的当前版本与先前的版本不向后兼容,则应该增加它:”您应该首先探索对序列化的广泛对象版本支持,(a)确保该类现在确实是序列化不兼容的方式,每个规范都很难实现;(b)尝试使用诸如自定义read / writeObject()方法,readResolve / writeReplace()方法,serializableFields声明之类的方案,以确保流保持兼容。改变现状serialVersionUID是万不得已,绝望的顾问。
user207421

4
当类的初始作者显式引入时,serialVersionUID的@EJP增量就会出现。我会说,jvm生成的序列ID应该没问题。这是我在序列化中看到的最佳答案
过度兑换

307

我不能错过这个机会来插上乔什·布洛赫(Josh Bloch)的书《有效的Java》(第二版)。第11章是有关Java序列化的必不可少的资源。

Per Josh会根据类名称,已实现的接口以及所有公共成员和受保护成员来生成自动生成的UID。以任何方式更改其中任何一项都会更改serialVersionUID。因此,仅在确定不会对一个以上版本的类进行序列化(跨进程或在以后的时间从存储中检索)时,您才无需弄乱它们。

如果你忽略他们,现在,以后,你需要更改类以某种方式,但保持兼容性瓦特/旧版本的类,你可以使用JDK工具发现的serialver生成serialVersionUID类,并明确设置在新班上。(根据您的更改,您可能还需要通过添加writeObjectreadObject方法来实现自定义序列化-请参阅Serializablejavadoc或前面提到的第11章。)


33
因此,如果有人担心类的旧版本的兼容性,可能会对SerializableVersionUID感到困扰?
Ziggy

10
是的,如果较新版本将任何公共成员更改为受保护,则默认的SerializableVersionUID将有所不同,并将引发InvalidClassExceptions。
Chander Shivdasani 2012年

3
类名,已实现的接口,所有公共和受保护的方法,所有实例变量。
Achow

31
值得注意的是,约书亚·布洛赫(Joshua Bloch)建议,对于每个 Serializable类,都应指定串行版本uid。引用第11章的内容: 无论选择哪种序列化形式,都应在所编写的每个可序列化类中声明一个显式串行版本UID。这样就消除了串行版本UID作为潜在的不兼容来源(项目74)。性能上也有一点好处。如果未提供串行版本的UID,则需要昂贵的计算才能在运行时生成一个。
2014年

136

您可以告诉Eclipse忽略以下serialVersionUID警告:

窗口>首选项> Java>编译器>错误/警告>潜在的编程问题

如果您不知道,您可以在本节中启用很多其他警告(甚至有一些报告为错误),许多警告非常有用:

  • 潜在的编程问题:可能的意外布尔分配
  • 潜在的编程问题:空指针访问
  • 不必要的代码:永远不会读取局部变量
  • 不必要的代码:冗余null检查
  • 不必要的代码:不必要的强制转换或'instanceof'

还有很多。


21
支持,但仅因为原始海报似乎没有序列化任何内容。如果张贴者说“我正在序列化这件事并...”,那么您会得到一票反对:P
约翰·加德纳

3
没错-我当时假设提问者根本就不想受到警告
马特b

14
@Gardner->同意!但是发问者也想知道为什么他可能不想被警告。
Ziggy

8
发问者显然在乎为什么应该有一个UID。因此,简单地告诉他忽略警告应该被否决。
cinqS

111

serialVersionUID便于序列化数据的版本控制。序列化时,其值与数据一起存储。反序列化时,将检查相同版本以查看序列化数据如何与当前代码匹配。

如果要版本化数据,通常以 serialVersionUID 0,然后对类进行每次结构更改,以改变序列化的数据(添加或删除非瞬态字段),然后对其进行修改。

内置的反序列化机制(in.defaultReadObject())将拒绝对旧版本的数据进行反序列化。但是,如果您愿意,可以定义自己的readObject()函数,该函数可以读回旧数据。然后,此自定义代码可以检查serialVersionUID,以了解数据所在的版本并决定如何对其进行反序列化。如果您存储在代码的多个版本中都可以生存的序列化数据,则此版本控制技术很有用。

但是存储序列化数据这么长的时间并不是很常见。使用序列化机制临时将数据临时写入例如缓存,或者通过网络将其发送到具有相同版本代码库相关部分的另一个程序,这是更为常见的做法。

在这种情况下,您对保持向后兼容性不感兴趣。您只关心确保正在通信的代码库确实具有相同版本的相关类。为了方便进行此类检查,您必须维护serialVersionUID像以前一样原样,并且在更改类时不要忘记对其进行更新。

如果您忘记更新字段,则可能会遇到具有不同结构但具有相同的类的两个不同版本serialVersionUID。如果发生这种情况,默认机制(in.defaultReadObject())将不会检测到任何差异,并尝试对不兼容的数据进行反序列化。现在,您可能会遇到神秘的运行时错误或静默故障(空字段)。这些类型的错误可能很难找到。

因此,为了帮助解决该用例,Java平台为您提供了不serialVersionUID手动设置选项。相反,将在编译时生成类结构的哈希并将其用作id。这种机制将确保您永远不会拥有具有相同ID的不同类结构,因此不会遇到上述难以跟踪的运行时序列化失败的情况。

但是自动生成的id策略有一个缺点。也就是说,同一类的生成ID可能在编译器之间有所不同(如上文Jon Skeet所述)。因此,如果在使用不同编译器编译的代码之间传递序列化数据,则建议仍然手动维护ID。

并且,如果您像提到的第一个用例那样向后兼容数据,则您可能还想自己维护ID。为了获得可识别的ID,并更好地控制它们的更改时间和方式。


4
添加或删除非临时字段不会使类的序列化不兼容。因此,没有理由在此类变化上“碰碰碰”。
user207421 2014年

4
@EJP:嗯?添加数据肯定会改变我世界中的序列化数据。
2014年

3
@AlexanderTorstling阅读我写的内容。我不是说它不会“更改序列化数据”。我说过“不会使类的序列化不兼容”。这不是同一回事。您需要阅读对象序列化规范的版本控制一章。
user207421 2014年

3
@EJP:我认识到添加一个非临时字段并不一定意味着您使类的序列化不兼容,但这是结构上的更改,它会更改序列化的数据,除非这样做,否则通常您希望更改版本,除非您处理向后兼容性,我还将在后面的文章中进行解释。您的意思是什么?
2014年

4
我的观点仍然完全是我所说的。添加或删除非临时字段不会使类Serialization不兼容。因此,您无需每次都更改serialVersionUID。
user207421

72

什么是serialVersionUID,为什么要使用它?

SerialVersionUID是每个类的唯一标识符,JVM使用它来比较该类的版本,以确保在反序列化期间加载序列化期间使用的同一类。

指定一个可以提供更多控制权,但是如果您未指定的话,JVM会生成一个控制权。生成的值在不同的编译器之间可能有所不同。此外,有时您只是出于某种原因希望禁止对旧的序列化对象[ backward incompatibility]进行反序列化,在这种情况下,您只需要更改serialVersionUID。

javadocsSerializable

默认的serialVersionUID计算对类详细信息高度敏感,类详细信息可能会根据编译器的实现而有所不同,因此可能会InvalidClassException在反序列化期间导致意外的。

因此,您必须声明serialVersionUID,因为它可以提供更多控制权

本文对此主题有一些好处。


3
@Vinothbabu,但serialVersionUID是静态的,因此静态变量无法序列化。那么jvm将如何检查版本,却不知道反序列化对象的版本是什么
Kranthi Sama 2014年

3
此答案中未提及的一件事是,您可能serialVersionUID不知不觉地盲目地这样做,可能会导致意想不到的后果。汤姆·安德森(Tom Anderson)对MetroidFan2002答案的评论解决了此问题:“我想说的是,如果您不使用序列化来永久存储,则应使用@SuppressWarnings而不是添加值。它会使类混乱,并且保留了类的能力。 serialVersionUID机制可保护您免受不兼容的更改的影响。”
2014年

7
serialVersionUID不是一个“用于每个类别的唯一标识符”。完全合格的类名称是那个。它是一个版本指示器。
user207421 2015年

58

最初的问题要求“为什么重要”和“示例”,在此Serial Version ID有用。好吧,我找到了一个。

假设您创建了一个Car类,将其实例化,然后将其写到对象流中。展平的汽车对象在文件系统中放置了一段时间。同时,如果Car通过添加新字段来修改类。稍后,当您尝试读取(即反序列化)展平的Car对象时,会得到java.io.InvalidClassException–,因为所有可序列化的类都会自动获得唯一的标识符。当类的标识符与展平对象的标识符不相等时,抛出此异常。如果您确实考虑过,则会由于添加了新字段而引发异常。您可以通过声明一个明确的serialVersionUID来控制版本,从而避免抛出此异常。明确声明您的serialVersionUID(因为不必计算)。因此,最佳实践是在创建它们后立即将自己的serialVersionUID添加到Serializable类,如下所示:

public class Car {
    static final long serialVersionUID = 1L; //assign a long value
}

并且应该为每个UID分配一个随机的长号,而不是1L。
阿巴斯(Abbas)

4
@abbas“一个应该”为什么这么做?请说明其区别。
user207421 '17

顾名思义,它表示用于序列化对象的类的版本。如果每次都给它分配相同的编号,则将找不到正确的类来反序列化它。因此,每当您在类中进行更改时,最好也更改版本。
阿巴斯(Abbas)

2
@abbas,这个意图与使用递增的自然数不冲突1,依此类推。
Vadzim

2
@BillK,我认为序列化检查绑定到一对classname和serialVersionUID。因此,不同类和库的不同编号方案不会产生任何干扰。还是暗指代码生成库?
Vadzim

42

首先,我需要解释什么是序列化。

序列化允许将对象转换为流,以便通过网络发送该对象,或者保存到文件或保存到DB以供使用。

有一些序列化规则

  • 仅当对象的类或其超类实现Serializable接口时,该对象才可序列化

  • 一个对象是可序列化的(本身实现了Serializable接口),即使其超类不是。但是,可序列化类的层次结构中的第一个超类(不实现Serializable接口)必须具有无参数构造函数。如果违反此规定,则readObject()将在运行时生成java.io.InvalidClassException

  • 所有原始类型都是可序列化的。

  • 暂态字段(带有暂态修饰符)未序列化(即,未保存或恢复)。实现Serializable的类必须标记不支持序列化的类(例如文件流)的瞬态字段。

  • 静态字段(带有static修饰符)未序列化。

Object被序列化,Java运行时关联的序列版本号又名的serialVersionID

我们需要serialVersionID的位置: 在反序列化过程中,验证发送者和接收者在序列化方面是否兼容。如果接收者用不同的类加载了类,serialVersionID那么反序列化将以结束InvalidClassCastException
可序列化的类可以serialVersionUID通过声明一个serialVersionUID必须为静态,最终且类型为long的字段来显式声明其自身。

让我们尝试一个例子。

import java.io.Serializable;    
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String empname;
private byte empage;

public String getEmpName() {
    return name;
}
public void setEmpName(String empname) {
    this.empname = empname;
}
public byte getEmpAge() {
    return empage;
}
public void setEmpAge(byte empage) {
    this.empage = empage;
}

public String whoIsThis() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}

创建序列化对象

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Writer {
public static void main(String[] args) throws IOException {
    Employee employee = new Employee();
    employee.setEmpName("Jagdish");
    employee.setEmpAge((byte) 30);

    FileOutputStream fout = new 
FileOutputStream("/users/Jagdish.vala/employee.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(employee);
    oos.close();
    System.out.println("Process complete");
}
}

反序列化对象

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Reader {
public static void main(String[] args) throws ClassNotFoundException, 
IOException {
    Employee employee = new Employee();
    FileInputStream fin = new 
    FileInputStream("/users/Jagdish.vala/employee.obj");
    ObjectInputStream ois = new ObjectInputStream(fin);
    employee = (Employee) ois.readObject();
    ois.close();
    System.out.println(employee.whoIsThis());
 }
}    

注意:现在,更改Employee类的serialVersionUID并保存:

private static final long serialVersionUID = 4L;

并执行Reader类。不执行Writer类,您将获得异常。

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)

37

如果您永远不需要将对象序列化为字节数组并发送/存储它们,则不必担心。如果这样做,则必须考虑您的serialVersionUID,因为对象的反序列化器会将其与其类加载器所具有的对象版本进行匹配。在Java语言规范中阅读有关它的更多信息。


10
如果您不打算序列化对象,为什么它们可以序列化?
erickson

7
@erickson-父类可能是可序列化的,例如ArrayList,但是您希望您自己的对象(例如,修改后的数组列表)将其用作基础,但是永远不会序列化您创建的Collection。
MetroidFan2002 2009年

5
Java语言规范中没有任何地方提到它。在对象版本控制规范中提到了它。
2014年

4
这是Java 8 Object Versioning Specification的链接。
罗勒·布尔克

34

如果在类上收到此警告,则永远不会考虑序列化,也不会声明自己 implements Serializable,这通常是因为您从实现了Serializable的超类继承而来。通常,最好委托给这样一个对象,而不要使用继承。

所以,代替

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

并在相关方法中调用myList.foo()代替this.foo()(或super.foo())。(这并不适合所有情况,但仍然很常见。)

我经常看到人们扩展JFrame之类的东西,而实际上他们只需要委托给它。(这也有助于在IDE中自动完成,因为JFrame具有数百种方法,当您要在类上调用自定义方法时就不需要这些方法。)

警告(或serialVersionUID)不可避免的一种情况是,当您从AbstractAction扩展时,通常是在一个匿名类中,仅添加actionPerformed方法。我认为在这种情况下不应该发出警告(因为您通常无法跨类的不同版本可靠地序列化和反序列化此类匿名类),但是我不确定编译器如何识别这一点。


4
我认为您认为正确无拘无束更有意义,尤其是在讨论诸如ArrayList之类的类时。但是,许多框架要求人们从可序列化的抽象超类(例如Struts 1.2的ActionForm类或Saxon的ExtensionFunctionDefinition)进行扩展,在这种情况下,这种解决方案是不可行的。我认为您是对的,如果在某些情况下忽略警告(例如,如果您从抽象的可序列化类扩展
过来

2
当然,如果您将一个类添加为成员,而不是从其继承,则必须为希望使用的成员类的每个方法编写一个包装器方法,这将使它在许多情况下不可行。除非Java具有类似于perl的功能__AUTOLOAD,但我不知道。
M_M 2012年

4
@M_M:当您将很多方法委派给包装的对象时,当然不适合使用委派。但是我认为这种情况是设计错误的征兆-类的用户(例如“ MainGui”)不需要调用包装对象(例如JFrame)的许多方法。
圣保罗Ebermann

1
我对委托不满意的是需要保留对委托的引用。每个参考都意味着更多的内存。如我错了请纠正我。如果我需要一个包含100个对象的CustomizedArrayList,那么这没什么大不了的,但是如果我需要几个对象的数百个CustomizdeArrayLists,则内存使用量将大大增加。
WVrock 2014年

2
Throwable是可序列化的,并且只有Throwable是可抛出的,因此无法定义不可序列化的异常。委派是不可能的。
菲尔(Phil)

22

要了解字段serialVersionUID的重要性,应该了解序列化/反序列化的工作方式。

序列化可序列化的类对象时,Java运行时会将序列号(称为serialVersionUID)与此序列化的对象关联。在反序列化此序列化对象时,Java Runtime会将序列化对象的serialVersionUID与该类的serialVersionUID进行匹配。如果两者相等,则仅继续进行反序列化,否则将引发InvalidClassException。

因此,我们得出的结论是,要使序列化/反序列化过程成功进行,序列化对象的serialVersionUID必须与该类的serialVersionUID等效。如果程序员在程序中显式指定了serialVersionUID值,则相同的值将与序列化的对象和类相关联,而与序列化和反序列化平台无关(例如,可以使用sun或MS JVM和反序列化可能使用Zing JVM在不同的平台Linux上)。

但是,如果程序员未指定serialVersionUID,则在对任何对象进行Serialization \ DeSerialization时,Java运行时将使用其自己的算法来计算它。这个serialVersionUID计算算法从一个JRE到另一个JRE有所不同。序列化对象的环境也可能使用一个JRE(例如:SUN JVM),反序列化发生的环境也可能使用Linux Jvm(zing)。在这种情况下,与序列化对象关联的serialVersionUID将与在反序列化环境下计算的类的serialVersionUID不同。反过来,反序列化将不会成功。因此,为避免此类情况/问题,程序员必须始终指定Serializable类的serialVersionUID。


2
该算法没有变化,但略有不足。
user207421 2014年

...算法没有变化,但是它的指定稍有不足...意味着任何jvm都可能变化..... @ user207421
AnthonyJClink

18

不用担心,默认计算确实很好,足以满足99,9999%的情况。而且,如果遇到问题,您可以-如前所述-在需要时引入UID(这不太可能)


2
垃圾。在班级没有变化的情况下就足够了。您有零证据支持“ 99.9999%”。
user207421 '18

18

作为示例,其中缺少的serialVersionUID可能会导致问题:

我正在研究由使用EJB模块的Web模块组成的Java EE应用程序。Web模块EJB远程调用该模块并传递POJO实现的Serializable为参数的。

这个 POJO's包装在EJB jar中,并且在Web模块的WEB-INF / lib中它自己的jar中。它们实际上是同一类,但是当我打包EJB模块时,我解包了这个POJO的jar,将其与EJB模块打包在一起。

对的调用EJB失败,并带有以下异常,因为我没有声明它serialVersionUID

Caused by: java.io.IOException: Mismatched serialization UIDs : Source
 (Rep.
 IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
 = 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
 = 6227F23FA74A9A52

16

我通常serialVersionUID在一种上下文中使用:当我知道它将离开Java VM的上下文时。

我会在使用ObjectInputStreamObjectOutputStream应用程序时知道这一点,或者如果知道使用的库/框架会使用它。serialVersionID确保不同版本或供应商的不同Java VM可以正确互操作,或者确保它在VM外部存储和检索(例如)HttpSession,即使重新启动和升级应用程序服务器,会话数据也可以保留。

对于所有其他情况,我使用

@SuppressWarnings("serial")

因为大多数时候默认值serialVersionUID就足够了。这包括ExceptionHttpServlet


它不包含可以换出它们的容器中的HttpServlet,也不包括RMI中的Exception。
user207421

14

字段数据代表存储在类中的一些信息。类实现Serializable接口,因此自动提供eclipse来声明serialVersionUID字段。让我们从此处设置的值1开始。

如果您不希望出现该警告,请使用以下命令:

@SuppressWarnings("serial")

11

如果CheckStyle可以验证实现Serializable的类上的serialVersionUID具有良好的值,即它与串行版本ID生成器所生成的值匹配,那将是很好的选择。例如,如果您的项目包含大量可序列化的DTO,记得记住删除现有的serialVersionUID并重新生成它是一件很麻烦的事,并且(目前我所知道的)唯一可以验证此方法的方法是为每个类重新生成并进行比较。旧的。这是非常非常痛苦的。


8
如果将serialVersionUID始终设置为生成器将产生的相同值,则根本不需要它。毕竟,当类仍然兼容时,其存在的理由是在更改后保持不变。
圣保罗Ebermann

4
原因是不同的编译器为相同的类提供相同的值。如javadocs(在上面也回答)中所述,生成的版本很脆弱,即使该类可以正确地反序列化,其版本也会有所不同。只要您每次在同一编译器上运行此测试,它就应该是安全的。如果您升级jdk并推出新规则,即使您的代码没有更改,上帝也会为您提供帮助。
Andrew Backer

2
不需要匹配serialver会产生的结果。-1
user207421 2014年

通常,根本不需要。@AndrewBacker的情况将需要在同一个.java文件上使用两个不同的编译器,并且.class文件的两个版本彼此通信-大多数情况下,您只是创建该类并进行分发。如果是这样,那么没有SUID会很好用。
比尔K

1
真正将序列化用于存储/检索目的的人员通常会将设置serialVersionUID为1。如果该类的较新版本不兼容,但仍要求能够处理旧数据,则可以增加版本号并向其中添加特殊代码处理较旧的格式。每当看到serialVersionUID大于1的数字时,我都会哭泣,要么是因为它是一个随机数(无用),要么是因为该类显然需要处理10个以上的不同版本。
john16384 '18

11

SerialVersionUID用于对象的版本控制。您也可以在类文件中指定serialVersionUID。不指定serialVersionUID的后果是,当您添加或修改类中的任何字段时,已序列化的类将无法恢复,因为为新类和为旧的序列化对象生成的serialVersionUID将有所不同。Java序列化过程依赖于正确的serialVersionUID来恢复序列化对象的状态,并在serialVersionUID不匹配的情况下抛出java.io.InvalidClassException

了解更多:http : //javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ


9

为什么在Java中使用SerialVersionUID内部Serializable类?

在期间serialization,Java运行时为类创建版本号,以便稍后可以反序列化。此版本号SerialVersionUID在Java中是已知的。

SerialVersionUID用于版本化序列化数据。如果类SerialVersionUID与已序列化的实例匹配,则只能对它进行反序列化。当我们不在SerialVersionUID类中声明时,Java运行时会为我们生成它,但不建议这样做。建议声明SerialVersionUIDprivate static final long变量以避免默认机制。

如果Serializable通过实现标记接口将类声明为java.io.Serializable,则Java运行时将使用默认的序列化机制将该类的实例持久化到磁盘中,前提是您尚未使用Externalizable接口自定义流程。

另请参见为什么在Java的Serializable类内使用SerialVersionUID

程式码:javassist.SerialVersionUID


8

如果要修改大量没有设置serialVersionUID的类,同时又要与旧类保持兼容,则IntelliJ Idea,Eclipse之类的工具会因为生成随机数而无法使用,无法在大量文件上使用一气呵成。我提出以下bash脚本(对于Windows用户感到抱歉,请考虑购买Mac或转换为Linux),以轻松解决serialVersionUID问题:

base_dir=$(pwd)                                                                  
src_dir=$base_dir/src/main/java                                                  
ic_api_cp=$base_dir/target/classes                                               

while read f                                                                     
do                                                                               
    clazz=${f//\//.}                                                             
    clazz=${clazz/%.java/}                                                       
    seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
    perl -ni.bak -e "print $_; printf qq{%s\n}, q{    private $seruidstr} if /public class/" $src_dir/$f
done

您保存此脚本,对您〜/ bin说add_serialVersionUID.sh。然后在Maven或Gradle项目的根目录中运行它,如下所示:

add_serialVersionUID.sh < myJavaToAmend.lst

该.lst包含用于以以下格式添加serialVersionUID的Java文件列表:

com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java

该脚本在后台使用JDK serialVer工具。因此,请确保$ JAVA_HOME / bin在PATH中。


2
给我一个主意:在发布之前,请始终使用这样的工具(永远不要手动)来重新生成串行版本uid-这样,您可以避免忘记对由于实际情况而更改了其串行版本uid的类进行更改不兼容的更改。手动跟踪该信息非常困难。
Ignazio

6

约书亚·布洛赫(Joshua Bloch)在《有效的Java》中很好地记录了这个问题。一本非常好的书,必读。我将在下面概述一些原因:

序列化运行时为每个可序列化的类提供了一个称为序列号的编号。此数字称为serialVersionUID。现在,该数字后面有一些数学运算,它基于类中定义的字段/方法得出。对于相同的类,每次都会生成相同的版本。在反序列化过程中使用此数字来验证序列化对象的发送者和接收者是否已为该对象加载了与序列化兼容的类。如果接收者已为该对象加载了一个与相应发送者类具有不同的serialVersionUID的类,则反序列化将导致InvalidClassException。

如果该类是可序列化的,则还可以通过声明一个名称为“ serialVersionUID”的字段来显式声明自己的serialVersionUID,该字段必须是静态的,最终的且类型为long。像Eclipse这样的大多数IDE都可以帮助您生成该长字符串。


6

每次对对象进行序列化时,都会在该对象上标记该对象的类的版本ID号。此ID称为serialVersionUID,它是基于有关类结构的信息计算的。假设您创建了一个Employee类,并且它的版本ID为#333(由JVM分配),现在,当您序列化该类的对象(假设Employee对象)时,JVM将为其分配UID为#333。

考虑一种情况-将来您需要编辑或更改您的类,并且在这种情况下,当您对其进行修改时,JVM将为其分配新的UID(假设#444)。现在,当您尝试反序列化员工对象时,JVM会将序列化对象(员工对象)的版本ID(#333)与该类的ID(即#444)进行比较(因为它已更改)。相比之下,JVM将发现两个版本的UID不同,因此反序列化将失败。因此,如果每个类的serialVersionID由程序员自己定义。即使该类将来进行了开发,它也将是相同的,因此即使更改了该类,JVM也会始终发现该类与序列化对象兼容。有关更多信息,请参见HEAD FIRST JAVA的第14章。


1
每次将对象的序列化时,serialVersionUID都会发送一次。并非随每个对象一起发送。
user207421 '17

3

简单说明:

  1. 您要序列化数据吗?

    序列化基本上是将类数据写入文件/流/等。反序列化将数据读回到类中。

  2. 您打算投产吗?

    如果您仅测试不重要/伪造的数据,则不必担心(除非您直接测试序列化)。

  3. 这是第一个版本吗?

    如果是这样,请设置serialVersionUID=1L

  4. 这是第二,第三等产品版本吗?

    现在您需要担心serialVersionUID,应该深入研究它。

基本上,如果在更新需要写入/读取的类时未正确更新版本,则在尝试读取旧数据时会出现错误。


2

“ serialVersionUID”是一个64位数字,用于在反序列化过程中唯一地标识类。序列化对象时,该类的serialVersionUID也会写入文件中。每当您反序列化此对象时,java运行时就会从序列化的数据中提取此serialVersionUID值,并将与该类关联的相同值进行比较。如果两者都不匹配,则将抛出“ java.io.InvalidClassException”。

如果可序列化的类未明确声明serialVersionUID,则序列化运行时将根据类的各个方面(例如字段,方法等)为该类计算serialVersionUID值,您可以参考此链接以进行演示应用程序。


1

简而言之,此字段用于检查序列化数据是否可以正确反序列化。序列化和反序列化通常由程序的不同副本进行-例如,服务器将对象转换为字符串,而客户端将收到的字符串转换为对象。该字段告诉双方,对于该对象是什么,它们具有相同的想法。该字段在以下情况下有帮助:

  • 您在不同的地方(例如1个服务器和100个客户端)有许多不同的程序副本。如果您要更改对象,更改版本号而忘记更新此客户端,则它将知道他无法反序列化

  • 您已将数据存储在某个文件中,然后稍后尝试使用带有修改对象的程序的更新版本打开它-如果您保持正确的版本,则将知道此文件不兼容

什么时候重要?

最明显的是-如果将一些字段添加到对象中,则较旧的版本将无法使用它们,因为它们的对象结构中没有这些字段。

不太明显-在反序列化对象时,字符串中不存在的字段将保留为NULL。如果您已从对象中删除了字段,则较旧的版本会将字段保留为allways-NULL,如果较旧的版本依赖于此字段中的数据,则可能导致行为异常(无论如何,您都是出于某种目的而创建它的:-))

最不明显-有时您更改了某些领域含义中的想法。例如,当您12岁时,“自行车”下的意思是“自行车”,但是当您18岁时,您的意思是“摩托车”-如果您的朋友邀请您“骑自行车穿越城市”,那么您将是唯一一个骑自行车,您将无法理解在各个领域保持相同含义的重要性:-)


1

首先回答您的问题,当我们不在类中声明SerialVersionUID时,Java运行时会为我们生成它,但是该过程对许多类元数据敏感,包括字段数,字段类型,字段访问修饰符,实现的接口因此,建议自行声明它,Eclipse会警告您同样的事情。

序列化:我们经常与重要对象一起工作,这些对象的状态(对象变量中的数据)是如此重要,以至于在将对象状态发送给其他对象的情况下,我们不会因为电源/系统故障(或网络故障)而丢失它机。此问题的解决方案称为“持久性”,它仅表示持久性(保存/保存)数据。序列化是实现持久性的许多其他方法之一(通过将数据保存到磁盘/内存)。保存对象的状态时,重要的是为对象创建一个标识,以便能够正确地读回它(反序列化)。此唯一标识是ID,是SerialVersionUID。


0

什么是SerialVersionUID?答案:-假设有两个人,一个来自总部,另一个来自ODC,都将分别执行序列化和反序列化。在这种情况下,为了验证ODC中的接收者是经过身份验证的人,JVM会创建一个唯一ID,称为SerialVersionUID。

根据场景,这是一个很好的解释,

为什么选择SerialVersionUID?

序列化:在序列化时,每个对象发送方JVM都会保存一个唯一标识符。JVM负责根据发送方系统中存在的相应.class文件生成该唯一ID。

反序列化:反序列化时,接收方JVM将与对象关联的唯一ID与本地类Unique ID进行比较,即JVM还将基于接收方系统中存在的相应.class文件创建唯一ID。如果两个唯一的ID都匹配,则将仅执行反序列化。否则,我们将获得Runtime Exception,说明InvalidClassException。这个唯一的标识符不过是SerialVersionUID

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.