为什么在Java中,对于ImmutableCollections,调用coll.contains(null)会失败?
因为设计团队(创建guava的人)决定,在他们的集合中,null是不需要的,因此他们的集合与null检查之间的任何交互,即使在这种情况下,都应该抛出异常,以便尽早向程序员突出显示不匹配。即使已经建立了行为(如核心运行时本身中现有实现(例如ArrayList和friends,以及javadoc)),明确地走另一条路,并表示非语言检查(这个梨是否属于这个苹果列表?)强烈建议只返回false而不是抛出异常。
换句话说,guava搞砸了。但是现在他们已经这样做了,回头可能会破坏向后兼容性。这真的不是很好-您正在用抛出的异常替换为false返回值;可能存在依赖NPE的代码(捕获它并执行与
contains(null)
返回false而不是抛出异常不同的操作)-但这是一个罕见的情况,而且guava总是打破向后兼容性。
如何在一般情况下正确检查集合中的空值?
通过调用
.contains(null)
,就像你现在所做的一样。Guava不能正确处理这个问题并不改变答案。你也可以问“如何向列表添加元素”,并反驳回答“好吧,你调用
list.add(item)
来实现”,说:“我有一个实现了List接口的程序,它会播放Rick Astley的歌曲而不是添加到列表中,所以我拒绝你的答案。”
这就是Java和接口的工作方式:你可以对它们进行实现,唯一需要遵循的是作者理解必须遵循的契约。
通常来说,一个编写得如此糟糕以至于无缘无故破坏契约的库是不流行的。但是Guava很受欢迎。非常受欢迎。这表明了一个简单的事实:没有哪个库是完美的。Guava的API设计通常相当不错(在我看来,比例如Apache commons库要好得多),团队积极花费大量时间讨论适当的API设计,其代码使用Guava编写的效果很好(即易于理解,少有意外,易于维护,易于测试,也可能容易突变以应对变化的需求——对于像“美观”或“优雅”代码这样模糊的术语,唯一有用的定义就是能做到这些事情的代码,其他一切都是毫无意义的审美空谈)。换句话说,他们正在积极尝试,并且通常会做得正确。
只是,在这种情况下不是这样的。解决方法是:`return item != null && coll.contains(item);` 将完成任务。
在支持guava的选择方面有一个主要的论点:'contract break'是一种隐式的中断 - 人们会期望.contains(null)
有效,并且总是返回false,但是在javadoc中没有明确说明必须这样做。相比之下,例如IdentityHashMap
在其.containsKey
等实现中使用标识等价性(a==b
)而不是值等价性(a.equals(b)
),这明确违反了j.u.Map
接口中规定的javadoc合同。IHM有一个很好的理由,并在javadoc中强调了差异,并解释了原因。Guava对于它们奇怪的null行为并不那么清楚,但是,关于java中的null有一个至关重要的事情:
它的含义模糊不清。有时它意味着“空”,这是一种糟糕的设计:您永远不应该编写 if (x == null || x.isEmpty())
- 这意味着某些 API 编码存在问题。如果 null 在语义上等同于某个值(例如 ""
或 List.of()
),那么您应该返回 ""
或 List.of()
,而不是 null。但是,在这样的设计中,list.contains(null) == false
)是有意义的。
但有时候null表示“未找到”,“不相关”,“不适用”或“未知”(例如,如果map.get(k)返回null,则表示未找到。不是“我为你找到一个空值”)。这与SQL中NULL的含义相符。在所有这些情况下,.contains(null)既不应该返回true也不应该返回false。如果我给你一个弹珠袋,并问你里面是否有一个蓝绿色的弹珠,而你不知道“蓝绿色”的含义,你不应该对我的询问回答“是”或“否”:任何一种回答都毫无意义。你应该告诉我这个问题无法被回答。这在Java中最好通过抛出异常来表示,这也正是Guava所做的。这也与SQL中的NULL的作用相符。在SQL中,v IN (x)返回3个值之一,而不是2个值:它可以解析为true、false或null。v IN (NULL)将解析为NULL而不是false。它回答了一个无法通过NULL值回答的问题,NULL的含义是:不知道。
换句话说,guava 对于 null 的含义做出了一种调用,显然与您的定义不匹配,因为您期望 .contains(null) 返回 false。我认为您的观点更符合惯用法,但重要的是,guava 的观点是不同的但也是一致的,而 javadoc 只是在暗示,但并没有明确要求 .contains(null) 返回 false。
这对于修改您的代码毫无用处,但希望它能给您提供一个心智模型,并回答您的问题“为什么会这样工作?”
.contains(null)
。 - Dániel Somogyi