java.io.InvalidClassException:

12
InvalidClassException: 本地类不兼容: 流类描述符 serialVersionUID = -196410440475012755, 本地类 serialVersionUID = -6675950253085108747

在以下情况下,我遇到了 InvalidClassException 异常。我的 EAR 安装在 4 个 WebSphere 应用服务器上,并且执行是共享的。有时候我会从实现 Serializable 接口的 POJO 类中获得 InvalidClassException 异常。请问这是什么原因造成的?我一点头绪都没有。

5个回答

40
当你将一个类实现为可序列化的,需要实现 java.io.Serializable 接口。编译器会查找一个名为“serialVersionUID”的静态最终字段,类型为 long。如果该类没有显式声明此字段,则编译器会创建一个这样的字段,并给它赋予一个 serialVersionUID 值,该值是根据与类相关的各个方面进行实现依赖计算的结果。这个计算依据Sun公司提供的对象序列化规范。但是,不保证在所有编译器实现中该值是相同的。
这个值用于检查类的序列化兼容性。在反序列化保存的对象时进行检查。序列化运行时验证发送方类(用于在流上保存对象状态的类)和接收方类(正在用于恢复对象的类,可能在其他系统上)的 serialVersionUID 是否完全相同。如果接收方系统加载了具有与序列化过程中使用的类不同 serialVersionUID 的类,则会出现 InvalidClassException。
注意-- 强烈建议在所有要使可序列化的类中明确声明和初始化类型为 long、名称为 'serialVersionUID' 的静态最终字段,而不是依赖于默认计算该字段值。这个计算非常敏感,可能因为发送方和接收方采用了不同的编译器实现而导致相同的类出现 InvalidClassException。
在大多数情况下,您只会为该字段使用“private”访问修饰符,因为声明通常仅适用于声明它的类,并且我们真的不需要将该字段继承到子类中或从外部访问它。所以,我们几乎没有理由不保持它为'private'。

1
你好,非常感谢您的回复。正如您所提到的,序列化UID是特定于编译器的。但在这里,我的编译器版本在所有系统中都是相同的,并且它是间歇性发生的。 - karthik
我现在也遇到了同样的问题,编译器版本不匹配是唯一的原因吗?我的两台计算机,即开发和发布机器上分别安装了Java 1.6.29和1.6.23版本。 - Akhilesh
谢谢你的解释,这些值有什么限制吗?比如说我能不能把它们都设置为零?另外,有人知道这个ID的目的是什么吗?难道只是为了检查类是否被正确解码吗?如果是这样,那为什么要这么敏感呢?为什么不只是对这些值进行哈希处理呢?我的意思是,如果我覆盖了这个字段,那么会不会有危险?我是否会冒着绕过编译器之间真正不兼容的测试的风险? - Alex
@Alex,这个id可以有任何值。请阅读我的回答。 - amod
最好不要使用Java序列化。有大约50种实现可以用于编组/解组数据。如果您在这些类中没有使用接口(即使是这样也没关系),您可以将其序列化为JSON并重新加载。如果使用了接口,那么在尝试进行编组时需要更多的工作,因为编组器需要知道要使用哪个实现。 - Christian Bongiorno
显示剩余3条评论

9
当您尝试反序列化一个使用与同一类的不兼容版本(通常是早期版本)进行序列化的对象时,会出现该异常。
如果您在实现Serializable接口的类中没有明确指定serialVersionUID,则将基于类的(非瞬态的)字段生成一个值。这样做是为了确保不会恢复部分对象(失败比盲目继续使用可能损坏的对象更好)。
在Web应用程序系统中,序列化的常见用途是用于会话:如果将值放入会话中,则很可能最终会对其进行序列化(以支持集群或仅获取持久会话)。
因此,请要么使所有类在版本之间兼容或者确保不恢复它们不会破坏您的应用程序(即不要以这种方式存储重要信息)。

2
如果有人再遇到与EJB TimerTask相关的问题 - 就像我刚刚遇到的一样 - 如果你正在使用WebSphere应用程序服务器,则以下是一个提示:
在配置文件的bin文件夹中有两个批处理文件/ shell脚本,可以列出和删除EJB定时器任务。在我的情况下,我有一些定时器任务,这些任务使用了不同的、过时的serialVersionUID来序列化对象。由于序列化的对象已经改变,我无法除掉它们。所以我只是使用了:
findEJBTimers.bat cancelEJBTimers.bat
然后你的定时器任务就不存在了,错误消息也不会再出现。在我的情况下,这正是我需要的,但要获取此信息很困难。

1

当一个对象被序列化时,serialVersionUID 会与其他内容一起被序列化。

稍后当它被反序列化时,从反序列化的对象中提取 serialVersionUID 并与加载类的 serialVersionUID 进行比较。

如果这些数字不匹配,则会抛出 InvalidClassException 异常。


0

好的,正如所要求的,我将给出一个例子和我的建议:

这里是一个列表,列出了各种PoJos序列化的性能指标。

您需要权衡性能和便利性。但是,既然我提到了JSON作为序列化手段,那么这里有一个微不足道的例子,它不会依赖于编译器。基本上,除非您在接收端更改了pojo的结构,否则编译它的时间/方式/位置都是完全无关紧要的(实际上,它甚至不必在两个JVM之间)。从链接中可以看出,JSON实际上是最慢的,而XML只是一个负载大头。但它们都具有普遍支持的决定性优势。XML甚至允许应用样式表。

    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.3.1</version>
        <scope>test</scope>
    </dependency>

代碼

  @Test
    public void testJSON() throws Exception {
        Foo expected = new Foo(1,"Christian",1000000.00d);
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        String testJson = gson.toJson(expected);

        System.out.println(testJson);

        Foo result = gson.fromJson(testJson, Foo.class);
        assertEquals(expected,result);

    }

    public static class Foo {

        private String name;
        private Integer age;
        private Double paycheck;

        public Foo(Integer age, String name, Double paycheck) {
            this.age = age;
            this.name = name;
            this.paycheck = paycheck;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Foo foo = (Foo) o;

            if (age != null ? !age.equals(foo.age) : foo.age != null) return false;
            if (name != null ? !name.equals(foo.name) : foo.name != null) return false;
            if (paycheck != null ? !paycheck.equals(foo.paycheck) : foo.paycheck != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + (age != null ? age.hashCode() : 0);
            result = 31 * result + (paycheck != null ? paycheck.hashCode() : 0);
            return result;
        }
    }

输出

{
  "name": "Christian",
  "age": 1,
  "paycheck": 1000000.0
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接