java - 为什么需要serialversionuid - 序列化serialversionuid




什么是serialVersionUID,我为什么要使用它? (14)

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

可序列化类Foo不声明long类型的静态最终serialVersionUID字段

什么是serialVersionUID ,为什么它很重要? 请显示缺少serialVersionUID会导致问题的示例。


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

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

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

Serializablejavadoc

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

因此,您必须声明serialVersionUID,因为它为我们提供了更多控制权

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


java.io.Serializable的文档可能与您将得到的解释一样好:

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

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

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


为什么在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


不要打扰,默认计算非常好,足以满足99,9999%的情况。 如果你遇到问题,你可以 - 正如已经说明的那样 - 引入UID作为需求出现(这是非常不可能的)


如果你在类上得到这个警告,你从未想过序列化,并且你没有声明自己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-method。 我认为在这种情况下不应该有警告(因为你通常无法在类的不同版本中对这些匿名类进行可靠的序列化和反序列化),但我不确定编译器如何识别它。


如果你的序列化只是因为你必须为了实现而序列化(谁关心你是否为HTTPSession序列化,例如......如果它存储与否,你可能不关心反序列化表单对象)那么你可以忽略这个。

如果您实际使用的是序列化,那么只有在您计划直接使用序列化存储和检索对象时才有意义。 serialVersionUID表示您的类版本,如果您的类的当前版本与其先前版本不向后兼容,则应增加它。

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


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

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

@SuppressWarnings("serial")

您可以告诉Eclipse忽略这些serialVersionUID警告:

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

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

  • 潜在的编程问题:可能的意外布尔赋值
  • 潜在的编程问题:空指针访问
  • 不必要的代码:永远不会读取局部变量
  • 不必要的代码:冗余空值检查
  • 不必要的代码:不必要的演员或'instanceof'

还有很多。


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

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

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

@SuppressWarnings("serial")

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


至于缺少serialVersionUID可能导致问题的示例:

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

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

由于我没有声明它的serialVersionUID ,因此对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

首先,我需要解释序列化。
序列化允许将对象转换为流,通过网络发送该对象或保存到文件或保存到DB以供字母使用。

序列化有一些规则

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

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

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

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

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

对象序列化时JAVA Runtime关联称为serialVersionID的序列版本号。

我们需要serialVersionID的地方:在反序列化期间验证发送方和接收方是否与序列化兼容。如果接收方加载了具有不同serialVersionID的类,则反序列化将以InvalidClassCastException结束。
可序列化类可以通过声明名为“serialVersionUID”的字段来显式声明其自己的serialVersionUID,该字段必须是static,final和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");
}
}

Deserializ对象

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)

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

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


如果你想修改大量没有设置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

你保存这个脚本,比如说add_serialVersionUID.sh给〜/ bin。然后在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中。


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

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





serialversionuid