为什么在 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 found、irrelevant、not applicable 或 unknown(例如,如果 map.get(k) 返回 null,这就是它的意思:未找到。不是 'I found an empty value for你')。这与 NULL 在例如SQL。在所有这些情况下,.contains(null) 应该既不返回 true 也不返回 false。如果我递给你一袋弹珠并问你里面是否有一块发霉的弹珠,而你不知道grue 是什么意思,你不应该回答yes 或no 来回答我的问题:任何一个答案都是毫无意义的猜测。你应该告诉我这个问题无法回答。在 java 中最好通过抛出来表示,这正是 guava 所做的。这也与 NULL 在 SQL 中的作用相匹配。在 SQL 中,v IN (x) 返回 3 个值之一,而不是 2 个值:它可以解析为 true、false 或 null。 v IN (NULL) 将解析为 NULL 而不是 false。它正在回答一个无法用 NULL 值回答的问题,可以理解为:不知道。
换句话说,guava 调用了 null 所暗示的内容,这显然与您的定义不匹配,因为您期望 .contains(null) 返回 false。我认为你的观点更惯用,但重点是,guava 的观点不同但也一致,javadoc 只是暗示但没有明确要求 .contains(null) 返回 false。
这对修复您的代码没有任何用处,但希望它能给您一个思维模型,并回答您“为什么它会这样工作?”的问题。