【问题标题】:Why does ImmutableCollection.contains(null) fail?为什么 ImmutableCollection.contains(null) 失败?
【发布时间】:2021-05-09 23:16:36
【问题描述】:

前面的问题: 为什么在 Java 中调用 coll.contains(null) 会因 ImmutableCollections 而失败?

我知道,不可变集合不能包含空元素,我不想讨论这是好是坏。

但是当我编写一个函数时,它需要一个(一般的,非显式的不可变的)集合,它在检查空值时会失败。为什么实现不返回 false(这实际上是“正确”答案)?

一般而言,如何正确检查 Collection 中的空值?

编辑: 进行了一些讨论(感谢评论者!)我意识到,我混淆了两件事:来自 guava 库的ImmutableCollection,以及java.util.List.of 返回的列表,是来自 ImmutableCollections 的一些类。但是,这两个类都会在 .contains(null) 上抛出 NPE。

我的问题在于 List.of 结果,但从技术上讲,guaves 实现也会发生同样的情况。

【问题讨论】:

  • 需要代码。你尝试了什么?你能告诉我们你是如何产生这个问题的吗?
  • 是的,由于多种原因,该集合违反了Liskov substitution principle,您遇到了其中一种情况以及类应努力不违反该原则的原因。解决这个特定问题没有干净的方法,您可以检查实际的对象类型,然后根据它进行不同的检查,但这也是代码异味。顺便说一句:您可以将项目添加到集合中,但是这个类会抛出异常,这也是个坏主意,但它就是这样。
  • @markspace 你重现了我所说的:在任何 ImmutableCollection(例如 ImmutableList)上调用 .contains(null)
  • @luk2302 感谢这些原则。那是我的问题,但我没有它的名字。我也知道它在插入时会引发错误(这就是它不可变的原因),但我觉得 contains 函数很奇怪......

标签: java collections immutability


【解决方案1】:

我对这次讨论感到痛苦!

在我编写最终成为 Guava 的第一个系列之前,这样做的系列一直是我的烦恼。如果你发现任何 Guava 集合仅仅因为你问了一个像 .contains(null) 这样完全无辜的问题而抛出 NPE,请提交一个错误!我们讨厌这种废话。

编辑:我非常难过,我不得不回去查看我 2007 年的变更列表,该变更列表首先创建了 ImmutableSet 并从字面上看到:

  @Override public boolean contains(@Nullable Object target) {
    if (target == null) {
      return false;
    }

啊啊啊。

【讨论】:

  • 那么,immutableList.contains(null) 应该按照 Guava 的规范/API 设计原则解析为 false 而不是抛出 NPE?我可以看到绝大多数遇到此问题的人并没有考虑提交错误并认为这是预期的行为(我当然这样做了!)
  • 对不起:谁遇到了什么?在这种情况下是否有 Guava 集合?
  • @KevinBourillion Brainfart。眼睛看到return false;,大脑解析throw new NullPointerException。大脑受到了应有的谴责。
  • 我松了一口气。但自从那条评论......我们找到了一个! EvictingQueue 一直在这样做。所以我们正在修复它。
【解决方案2】:

为什么在 Java 中调用 coll.contains(null) 对于 ImmutableCollections 会失败?

因为设计团队(创建 guava 的团队)决定,对于他们的收藏,null 是不需要的,因此他们的收藏和 null 检查之间的任何交互,即使在这种情况下,也应该突出显示尽早向程序员表明存在不匹配。即使已建立的行为(根据核心运行时本身中的现有实现,例如 ArrayList 和朋友,以及 javadoc),也明确地相反,说不合理的检查(这是 pear 的一部分这个苹果列表?)强烈建议正确的做法是只返回false而不是扔掉。

换句话说,番石榴搞砸了。但是现在他们已经这样做了,返回可能会破坏向后兼容性。这真的不是很好 - 您正在用 false 返回值替换抛出的异常;据推测,可能存在依赖于 NPE 的代码(捕获它并执行与 contains(null) 返回 false 而不是抛出的代码所做的不同的事情) - 但这是一种罕见的情况,番石榴一直在破坏向后兼容性。

一般而言,我如何正确检查集合中的空值?

拨打.contains(null),就像你一样。番石榴做得不对的事实并没有改变答案。您不妨问“我如何将元素添加到列表中”,并反驳“好吧,您调用 list.add(item) 来做到这一点”的答案:嗯,我有这个 List 接口的实现,它可以播放 Rick Astley发言者而不是添加到列表中,所以,我拒绝你的回答。

这就是.. java 和接口的工作原理:您可以拥有它们的实现,并且它们按照接口要求执行的唯一保护是作者了解需要遵守的合同。

现在,通常情况下,一个写得如此糟糕以至于无缘无故违反合同的库*,并不受欢迎。但是番石榴很受欢迎。非常受欢迎。这得到了一个简单的事实:没有图书馆是完美的。 Guava 的 API 设计总体上非常好(在我看来,大大优于例如 Apache 公用库),并且团队积极地花费大量时间讨论适当的 API 设计,从某种意义上说,使用 guava 编写很好(定义为:易于理解、几乎没有惊喜、易于维护、易于测试,并且可能易于变异以应对不断变化的需求——对于像“nice”或“优雅”的代码——它是做这些事情的代码,其他任何东西都是毫无意义的美学胡言乱语)。换句话说,他们正在积极尝试,而且通常做对了。

只是,在这种情况下不是。解决它:return item != null && coll.contains(item); 将完成工作。

有一个支持 guava 选择的主要论据:他们的“合同中断”是一种隐含的中断 - 人们会认为 .contains(null) 有效,并且总是返回 false,但它没有明确声明在 javadoc 中必须这样做。对比例如IdentityHashMap,在其 .containsKey 等实现中使用身份等价 (a==b) 而不是值等价 (a.equals(b)),这明确违反了 @987654334 中所述的 javadoc 合同@ 界面。 IHM 有一个很好的理由,并在 javadoc 中突出了差异,并解释了原因。 Guava 对它们奇怪的 null 行为并不十分清楚,但是,关于 java 中 null 的一个关键点是:

它的意思是模糊的。有时它意味着“空”,这是糟糕的设计:你永远不应该写if (x == null || x.isEmpty())——这意味着某些 API 编码错误。如果 null 在语义上等同于某个值(例如 ""List.of()),那么您应该只返回 ""List.of(),而不是 null。但是,在这样的设计中,list.contains(null) == false) 是有意义的。

但有时 null 表示 not foundirrelevantnot applicableunknown(例如,如果 map.get(k) 返回 null,这就是它的意思:未找到。不是 'I found an empty value for你')。这与 NULL 在例如SQL。在所有这些情况下,.contains(null) 应该既不返回 true 也不返回 false。如果我递给你一袋弹珠并问你里面是否有一块发霉的弹珠,而你不知道grue 是什么意思,你不应该回答yesno 来回答我的问题:任何一个答案都是毫无意义的猜测。你应该告诉我这个问题无法回答。在 java 中最好通过抛出来表示,这正是 guava 所做的。这也与 NULL 在 SQL 中的作用相匹配。在 SQL 中,v IN (x) 返回 3 个值之一,而不是 2 个值:它可以解析为 truefalsenullv IN (NULL) 将解析为 NULL 而不是 false。它正在回答一个无法用 NULL 值回答的问题,可以理解为:不知道。

换句话说,guava 调用了 null 所暗示的内容,这显然与您的定义不匹配,因为您期望 .contains(null) 返回 false。我认为你的观点更惯用,但重点是,guava 的观点不同但也一致,javadoc 只是暗示但没有明确要求 .contains(null) 返回 false。

这对修复您的代码没有任何用处,但希望它能给您一个思维模型,并回答您“为什么它会这样工作?”的问题。

【讨论】:

  • 有帮助,谢谢。几点评论: IHM 有“奇怪”的行为,但它以非常明确的方式说明了这一点。另一方面,我花了一段时间才发现 ImmutableCollection 是我问题的根源……我想我没想到 java.util 包中有这样的行为。此外,您的代码 x == null || x.isEmpty() 还不够,我真的需要知道天气 null 是否在集合中(当然,如果它不是不可变的)。我想我需要一个 instanceof..
  • 和你的弹珠想法:如果你给我grue 我希望你给我一个相等函数,我只检查areEqual(e, grue) 我的每个元素。如果其中任何一个返回 true,则返回 true,否则返回 false
  • 等等,@DánielSomogyi - 你在干什么? ImmutableCollection 不在 java.util 中。它是番石榴库的一部分:当然我们在谈论this ImmutableCollection,对吧? null 不能在集合中。假设是番石榴 ImmutableCollection 是不可能的。 add 抛出。 j.u.Collection 接口中的 contains 方法不允许使用相等函数,因此您无法逃避该答案。
  • 是的,首先我把事情搞混了。我认为 javas ImmutableCollection(例如从 java.util.List.of() 返回)和 guaves ImmutableCollection 是相同的。 (在寻找解决方案时把事情搞混了)。我也知道你不能处理任何包含函数的相等性,这只是我对如何处理“grue”的想法。恕我直言,这是 Java 的缺陷,一般的相等函数取决于两个非空对象之一,您甚至必须知道哪个对象(例如 null.equals(a) 失败)。
  • 所以呃……这里没有人想过检查对番石榴的指控是否真实? :-)
猜你喜欢
  • 2021-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-16
  • 2015-06-19
  • 2014-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多