理解此警告:可序列化类不声明静态最终serialVersionUID

时间:2009-10-03 21:14:50

标签: java generics instance-initializers

我有一些静态初始化代码:

someMethodThatTakesAHashMap(new HashMap<K, V>() {
{
  put("a","value-a"); 
  put("c","value-c");}
});

出于某种原因,我收到了Eclipse的警告: 可序列化类不声明静态最终serialVersionUID。

这是抱怨匿名课吗?我能做些什么,或者我应该压制它。

6 个答案:

答案 0 :(得分:28)

您使用的语法称为double-brace initialization - 实际上是“instance initialization blockanonymous inner class的一部分(当然不是黑客)。因此,在使用此表示法时,您实际上是在定义一个新类(!)。

您的案例中的“问题”是HashMap实施Serializable。此接口没有任何方法,仅用于标识可序列化的语义。换句话说,它是一个标记界面,你具体地不需要实现任何东西。 ,在反序列化期间,Java使用名为serialVersionUID的版本号来验证序列化版本是否与目标兼容。如果您未提供此serialVersionUID,则会计算出来。并且,正如Serializable的javadoc中所记录的那样,计算出的值非常敏感,因此建议明确声明它以避免任何反序列化问题。这就是Eclipse“抱怨”的事情(注意这只是一个警告)。

因此,为了避免此警告,您可以向您的匿名内部类添加serialVersionUID

someMethodThatTakesAHashMap(new HashMap<String, String>() {
    private static final long serialVersionUID = -1113582265865921787L;

    {
        put("a", "value-a");
        put("c", "value-c");
    }
});

但是你松开了语法的简洁性(你可能甚至不需要它)。

因此,另一个选项是通过向您调用@SuppressWarnings("serial")的方法添加someMethodThatTakesAHashMap(Map)来忽略警告。这似乎更适合你的情况。

尽管如此,虽然这种语法简洁,但它有一些缺点。首先,如果对使用双括号初始化初始化的对象持有引用,则隐式保存对外部对象的引用,该引用将不符合垃圾回收的条件。所以要小心。第二(这听起来像微优化),双括号初始化有一个very a little bit of overhead。第三,这种技术实际上使用了我们所看到的匿名内部类,因此吃掉了一些permgen空间(但我怀疑这确实是一个问题,除非你真的滥用它们)。最后 - 这可能是最重要的一点 - 我不确定它是否使代码更具可读性(这不是一个众所周知的语法)。

所以,虽然我喜欢在测试中使用它(为了简洁),但我倾向于避免在“常规”代码中使用它。

答案 1 :(得分:6)

是的,你可以取消警告,但我会像这样重写:

HashMap<String, String> map  = new HashMap<String, String>();
map.put("a","value-a"); 
map.put("c","value-c");
someMethodThatTakesAHashMap(map);

IMO不需要抑制,也更好阅读。

答案 2 :(得分:0)

我普遍同意Bart K.,但出于提供信息的目的:
通过添加字段也可以消除警告,该字段可以通过按ctrl + 1自动生成 通过在定义之前添加@SuppressWarnings(“serial”)注释,也可以抑制警告 匿名类实现Serializeable,Serializeable需要此静态字段,以便在序列化和反序列化时可以区分版本。更多信息:
http://www.javablogging.com/what-is-serialversionuid/

答案 3 :(得分:0)

Google Collections库中的ImmutableMap类对于这种情况很有用。 e.g。

someMethodThatTakesAHashMap(ImmutableMap.<K, V>builder().put("a","value-a").put("c","value-c").build());

someMethodThatTakesAHashMap(ImmutableMap.of("a","value-a","c","value-c"));

答案 4 :(得分:0)

要解决问题的另一半,“我应该压制它吗?” -

是。在我看来,这是一个可怕的警告。 serialVersionUID应默认使用 not ,而不是相反。

如果你不添加serialVersionUID,最糟糕的事情是实际序列化兼容的两个版本的对象被认为是不兼容的。 serialVersionUID是一种声明序列化兼容性没有改变的方法,它覆盖了Java的默认评估。

使用serialVersionUID,最糟糕的事情是当类的序列化表单以不兼容的方式更改时,您无意中无法更新ID。充其量,您还会收到运行时错误。最糟糕的是,会发生更糟糕的事想象一下,更新它是多么容易。

答案 5 :(得分:0)

您的意图是初始化HashMap的匿名实例。警告提示您的代码比您预期的要多。

我们正在寻找的是一种初始化匿名HashMap实例的方法。我们上面创建的HashMap的匿名子类然后创建该匿名类的匿名实例。

因为代码比预期更多,我称之为黑客。

我们真正想要的是这样的:

foo(new HashMap<String, String>({"a", "value-a"}, {"c", "value-c"}));

但是这不是有效的Java。使用键/值对数组以类型安全的方式无法做任何事情。 Java简单没有表现力。

Google Collection的ImmutableMap.of静态方法很接近,但它意味着为不同数量的键/值对创建工厂方法的版本。 (见finnw的回答。)

所以保持简单。除非你的代码充满了这个初始化,否则请使用Bart K的解决方案。如果是这样,请使用ImmutableMap。或者使用“of”样式工厂方法滚动自己的HashMap子类。或者在实用程序类中创建这些“样式”工厂方法。这是两个键/值对的一个:

public final MapUtil {
    public static <K,V> Map<K,V> makeMap(K k1, V v1, K k2, V v2) {
        Map<K,V> m = new HashMap<K,V>();
        m.put(k1, v1);
        m.put(k2, v2);
        return m;
    }
}

接受冗长的事情,并在你的公司同事戴着与你一样的枷锁的知识中获得安慰。