【问题标题】:Understanding this warning: The serializable class does not declare a static final serialVersionUID了解此警告:可序列化类未声明静态最终 serialVersionUID
【发布时间】:2010-12-03 15:31:41
【问题描述】:

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

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

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

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

【问题讨论】:

  • 查看您最近的帖子:stackoverflow.com/questions/1514866/…
  • 是的,我把它移到了这里,所以如果其他人有同样的问题,他们可以在这里参考答案。为了其他开发人员的进步,您将答案移到这里会很棒。否则答案将在洗牌中丢失,未来的搜索者将无法轻松找到它。
  • 我的想法也是如此,所以我也在这里发布了我的部分答案。

标签: java generics instance-initializers


【解决方案1】:

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

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

IMO 无需压制,而且更易于阅读。

【讨论】:

  • 匿名类方式在 Java 社区中是否被视为一种 hack?人们编写这样的代码是一种非标准的方式吗?在 C# 中它是标准的,但在罗马 :)
  • 在问了这个相关问题后,我觉得这是对 Map 和 List 初始化的一种破解:stackoverflow.com/questions/924285/…。人们真的会喜欢干净的 Java 语法,比如 Ruby 或 Scala 或 Groovy,而“双大括号初始化方法”可以让你到达那里,尽管你的类路径中有很多额外的垃圾。
  • @GordonG:我认为它在 Java 中的使用并不多。但我不会说打电话是“黑客”。我不知道 C#,但在 C# 版本中,类(在您的示例中为 HashMap)是否也得到扩展?还是 C# 有一种更“更友好/更容易”的方式来做到这一点?
【解决方案2】:

我大体上同意 Bart K.,但仅供参考:
警告也可以通过添加字段来消除,按ctrl+1可以自动生成。
也可以通过在定义前添加 @SuppressWarnings("serial") 注释来抑制警告。
匿名类实现了Serializeable,Serializeable需要这个静态字段,以便在序列化和反序列化时可以区分版本。更多信息在这里:
http://www.javablogging.com/what-is-serialversionuid/

【讨论】:

    【解决方案3】:

    您使用的语法称为double-brace initialization - 实际上是“instance initialization block”,它是anonymous 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");
        }
    });
    

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

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

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

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

    【讨论】:

    • 1) “注意这只是一个警告”
    • @Laith 1) 事实是,如果您不打算序列化创建的集合,那么这确实是一个问题 2) 不确定您在这里显示什么(它不是双括号初始化也不是匿名内部类),但确实有很好的用例(例如,使用 JMock 时)3)这是一个任意决定(我经常使用默认计算值)。
    • @Pascal 2) 我一直尝试这样做,但从来没有成功,现在我知道可以用双括号来完成。 3) 你如何获得这个价值?
    • @Laith 和 serialver java.sun.com/javase/6/docs/technotes/tools/solaris/… 但任何体面的 IDE 都会为您做到这一点。
    • @Laith 这在快速修复选项中可用(Ctrl+1 对问题或单击左侧栏中的警告符号):添加生成的序列版本 ID
    【解决方案4】:

    来自 Google Collections 库的 ImmutableMap 类对于这种情况很有用。例如

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

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

    【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

        【解决方案6】:

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

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

        由于代码的作用超出了预期,我将其称为 hack。

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

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

        但这不是有效的 Java。没有办法使用键/值对数组以类型安全的方式执行此操作。 Java simple 没有表达能力。

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

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

        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;
            }
        }
        

        拥抱冗长,并在得知您的公司同事与您戴着同样的枷锁时感到安慰。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-02-09
          • 2014-05-05
          • 2014-01-24
          • 2013-08-29
          • 2011-01-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多