NPE in Java HashSet.isEmpty()



我在java.util.HashSet.isEmpty()的第191行得到了一个NPE。 我想,这一行只是在内部映射字段上调用 isEmpty()。

令我惊讶的是,这个字段是暂时的,所以在反序列化后,它将为空。 但这难道不是反序列化集合以取回值的目的吗? 在我看来,反序列化是此字段对于 HashSet 实例为空的唯一方法。

可能,我在这里错过了一些东西。有人可以解释一下吗?

Linux 上的 Java 1.8.0_151(3.13.0-61-泛型)在 amd64 平台上

以下是来自 JDK 的HashMap.isEmpty实现:

/**
* Returns <tt>true</tt> if this set contains no elements.
*
* @return <tt>true</tt> if this set contains no elements
*/
public boolean isEmpty() {
return map.isEmpty(); // line 191
}

编辑/附加信息:

  • 不幸的是,我无法举出一个最小的例子来证明这个问题。它发生在运行数小时的复杂系统的回归测试期间。所以,我对这套会发生什么一无所知或控制。此外,这个问题是不确定的,这意味着我们偶尔会看到它,但不是每次都看到它。
  • 我可以很好地提供一段调用isEmpty()的代码。但是 NPE 发生在库中,因此此方法显然是在现有实例上调用的,即不在null上调用。因此,提供此代码段无济于事
  • 我们的代码是多线程的。但乍一看,这部分只有单线程访问
  • 我们的代码在许多地方捕获并吞噬异常。这可能隐藏真正的罪魁祸首

由于map字段未final,因此在多线程代码中不正确地发布新构造的HashSet实例时,无法保证看到它处于初始化状态。而"新构造"意味着自构造以来没有发生其他操作,确保所涉及的线程之间的内存可见性,这原则上可能是任意的很长时间。

在对象反序列化期间,readObject方法负责重新初始化map字段并放回元素。这对于从持久窗体中隐藏这些实现详细信息是必需的。此外,反序列化的对象可能具有与序列化时不同的哈希代码(例如,从java.lang.Object继承的哈希代码为例)。所以无论如何都必须重新插入它们。大多数集合的一般原则是隐藏实现细节,并在专用writeObjectreadObject方法中逐个序列化和反序列化每个包含的元素。

因此,未来的HashSet实现可能是真正的哈希集,而不是在后台使用HashMap,而不会影响序列化兼容性。

最新更新