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

可序列化类Foo未声明静态finallong类型的serialVersionUID字段

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


当前回答

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

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

序列化有一些规则。

只有当对象的类或其超类实现serializable接口时,对象才可串行化对象是可序列化的(它本身实现了可序列化接口),即使它的超类不是。但是,可序列化类层次结构中的第一个超类(未实现可序列化接口)必须具有无参数构造函数。如果违反了这一点,readObject()将在运行时生成java.io.InvalidClassException所有基元类型都是可序列化的。瞬态字段(带有瞬态修饰符)未序列化(即未保存或还原)。实现Serializable的类必须标记不支持序列化的类的临时字段(例如,文件流)。静态字段(带有静态修饰符)未序列化。

当Object被序列化时,Java Runtime将序列版本号(即serialVersionID)关联起来。

我们需要serialVersionID的地方:

在反序列化期间,验证发送方和接收方在序列化方面是否兼容。如果接收方使用不同的serialVersionID加载了类,则反序列化将以InvalidClassCastException结束。可序列化类可以通过声明一个名为serialVersionUID的字段来显式声明自己的serialVersionID,该字段必须是静态、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() {
        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)

其他回答

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

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

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

Serializable的javadocs表示:

默认的serialVersionUID计算对类高度敏感详细信息可能因编译器实现而异因此,在反序列化。

因此,您必须声明serialVersionUID,因为它给了我们更多的控制权。

这篇文章在这个话题上有一些好的观点。

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

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

ANY-ACCESS-MODIFIER静态最终长序列版本UID=42L;

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

例如,缺少serialVersionUID可能会导致问题:

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

这个POJO的类被打包在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

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

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

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