当缺少serialVersionUID时,Eclipse会发出警告。


可序列化的类Foo没有声明静态final
longVersion类型的serialVersionUID字段


serialVersionUID是什么,为什么?重要?请举一个缺少serialVersionUID会引起问题的示例。

评论

找到有关serialversionUID的良好实践; dzone.com/articles/what-is-serialversionuid

#1 楼

java.io.Serializable的文档可能会为您提供大约很好的解释:


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


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


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


评论


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

–Ziggy
09年1月1日在7:27

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

–乔恩·斯基特(Jon Skeet)
09年1月1日于20:26

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

–JohnMerlino
2014年6月15日17:12



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

–乔恩·斯基特(Jon Skeet)
2014年6月15日18:43

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

– zpon
2014年10月8日,11:48

#2 楼

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

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

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

评论


我想说的是,如果您不使用序列化来永久存储,则应使用@SuppressWarnings而不是添加值。它减少了类的混乱,并且保留了serialVersionUID机制的功能,以保护您免受不兼容的更改的影响。

–汤姆·安德森(Tom Anderson)
2011年4月24日上午10:43

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

–MetroidFan2002
2011-4-25 17:09

@ MetroidFan2002:我认为@TomAnderson的serialVersionUID观点可以保护您免受不兼容的更改的影响。如果您不希望使用该类进行永久存储,则使用@SuppressWarnings可以更好地记录意图。

– AbdullahC
2011-12-30 14:32

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

–user207421
2012年12月12日,0:59

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

–过度兑换
2014年12月24日15:39



#3 楼

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

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

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

评论


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

–Ziggy
09年1月1日在7:28

是的,如果较新版本将任何公共成员更改为protected,则默认的SerializableVersionUID将有所不同,并将引发InvalidClassExceptions。

– Chander Shivdasani
2012年1月13日19:49

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

– Achow
13年1月1日在4:38

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

–阿舒托什·金达尔(Ashutosh Jindal)
2014年7月8日在13:25



#4 楼

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


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


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


潜在的编程问题:可能是偶然的布尔值分配
潜在的编程问题:空指针访问
不必要的代码:从未读取局部变量
不必要的代码:冗余的空检查
不必要的代码:不必要的强制转换或'instanceof '

等等。

评论


支持,但仅因为原始张贴者似乎没有序列化任何内容。如果张贴者说“我正在序列化这件事并...”,那么您将获得一票反对:P

–约翰·加德纳
08年11月13日在1:28

是的-我以为发问者根本不想被警告

–马特b
08年11月13日在2:52

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

–Ziggy
09年1月1日在7:29

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

– cinqS
17-4-18在3:31



#5 楼

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

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

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

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

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

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

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

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

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

评论


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

–user207421
2014年2月7日在1:05

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

–亚历山大·托斯汀
2014年2月7日在19:12



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

–user207421
2014年5月20日在21:27



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

–亚历山大·托斯汀
2014年5月21日下午6:42

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

–user207421
2015年5月4日11:16



#6 楼


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


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

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

Serializable的Java文档说:


默认的serialVersionUID计算对类高度敏感
详细信息可能会因编译器实现而异,并且可以
因此在反序列化期间导致意外的InvalidClassException s。


因此,必须声明serialVersionUID,因为它可以提供更多控制权。本文对此主题有一些要点。

评论


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

– Kranthi Sama
2014年6月16日11:06



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

–柯比
2014年6月17日下午16:30

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

–user207421
2015年6月9日下午4:17

#7 楼

最初的问题要求“为什么这很重要”和“示例”,以说明此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。

–阿巴斯
17-10-27在1:34

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

–user207421
17年11月25日在8:58

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

–阿巴斯
17年11月27日在0:39

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

–Vadzim
17年12月14日在18:21

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

–Vadzim
18年1月26日在10:26



#8 楼

首先,我需要解释什么是序列化。
序列化允许将对象转换为流,以便通过网络发送该对象,或者保存到文件或保存到DB以供使用。
有一些规则用于序列化。


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


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


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


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


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

当对Object进行序列化时,Java Runtime将序列号又称为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() {
        return getEmpName() + " is " + getEmpAge() + "years old";
    }
}
 

创建序列化对象
 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)


评论


如果我错了,请纠正我-本地类是您当前在类路径中使用的类,流是另一方使用的类(例如,服务器返回您的答案并序列化了响应)。与更新了其第三方库的服务器通信时,您可能会遇到这种情况,但是您(客户端)没有这样做。

–维克多
6月3日13:03

#9 楼

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

评论


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

– erickson
08年11月13日在5:34

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

–MetroidFan2002
2009年3月30日14:12

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

–user207421
2014年2月7日在1:10

这是指向Java 8 Object Versioning Specification的链接。

–罗勒·布尔克
2014年11月3日,下午4:12

#10 楼

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

因此,而不是

public class MyExample extends ArrayList<String> {

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


do

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方法。我认为在这种情况下不应该发出警告(因为您通常无法跨类的不同版本可靠地序列化和反序列化此类匿名类),但是我不确定编译器如何识别这一点。 />

评论


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

–piepera
2011-12-7 15:20



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

– M_M
2012年8月19日在22:15

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

–PaŭloEbermann
2012年8月20日在8:09

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

– WVrock
2014年11月29日在7:14

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

–菲尔
17年4月13日在2:59

#11 楼

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

序列化可序列化的类对象时,Java Runtime会将序列号(称为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。

评论


该算法没有变化,但略有不足。

–user207421
2014年5月20日在21:35

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

– AnthonyJClink
19年8月30日在1:01



#12 楼

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

我正在研究由使用EJB模块的Web模块组成的Java EE应用程序。该Web模块远程调用EJB模块,并传递一个实现POJO作为参数的Serializable。网络模块。它们实际上是同一类,但是当我打包EJB模块时,我解压缩了POJO的jar,将其与EJB模块打包在一起。

POJO's的调用失败,出现以下异常,因为我尚未声明其EJB

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


#13 楼

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

评论


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

–user207421
18年3月22日在10:30

问题不在于它不是“好”,而是不能保证在不同版本上保持一致。

–多里安·格雷(Dorian Gray)
11月3日15:44



#14 楼

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

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

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

@SuppressWarnings("serial")


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

评论


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

–user207421
2014年5月20日在21:36

#15 楼

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

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

@SuppressWarnings("serial")


#16 楼

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

评论


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

–PaŭloEbermann
2011-2-20在2:04

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

–安德鲁·贝克尔(Andrew Backer)
2011年8月9日在8:29

不需要匹配serialver将产生的内容。 -1

–user207421
2014年2月7日在1:10



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

– Bill K
18年1月25日在21:06

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

–john16384
18-2-10在11:11



#17 楼


#18 楼

为什么要在Java的SerialVersionUID类中使用Serializable

serialization期间,Java运行时会为一个类创建一个版本号,以便以后可以反序列化。此版本号在Java中称为SerialVersionUID

SerialVersionUID用于对序列化数据进行版本控制。仅当SerialVersionUID与已序列化的实例匹配时,才可以反序列化一个类。当我们不在类中声明SerialVersionUID时,Java运行时会为我们生成它,但不建议这样做。建议将SerialVersionUID声明为private static final long变量,以避免使用默认机制。

通过实现标记接口Serializable将一个类声明为java.io.Serializable时,如果您尚未使用Externalizable接口自定义该过程,则Java运行时将使用默认的序列化机制将该类的实例持久化到磁盘中。 >
另请参见为什么在Java的Serializable类内使用SerialVersionUID

#19 楼

如果要修改大量没有设置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中。

评论


给我一个主意:在发布之前,请始终使用这样的工具重新生成序列化版本的uid,而绝不手工操作-这样,您可以避免忘记对序列化版本uid因实际更改而应该更改的类进行更改不兼容的更改。手动跟踪该信息非常困难。

– Ignazio
16年3月17日在18:05

#20 楼

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

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

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

#21 楼

每次对对象进行序列化时,都会在该对象上标记该对象的类的版本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章。

评论


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

–user207421
17年11月25日9:00

#22 楼

一个简单的解释:



您要序列化数据吗?

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


您打算投入生产吗?

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


这是第一个版本吗?

如果是,请设置serialVersionUID=1L


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

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

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

#23 楼

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

如果可序列化的类未明确声明serialVersionUID,则序列化运行时将基于各种方法计算该类的serialVersionUID值。类的各个方面,例如字段,方法等,您可以在演示应用程序中引用此链接。

#24 楼

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


您在不同位置(例如1个服务器和100个客户端)具有许多不同的程序副本。如果您要更改对象,更改版本号而忘记更新一个该客户端,它将知道他无法反序列化
,您已将数据存储在某个文件中,随后尝试使用具有修改对象的程序的更新版本-如果您保持正确的版本,您将知道此文件不兼容

什么时候重要?

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

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

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

#25 楼

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

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

#26 楼

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

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

为什么使用SerialVersionUID?

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

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