为什么java.util.Map.containsKey会对不';不支持空键?

为什么java.util.Map.containsKey会对不';不支持空键?,java,hashmap,treemap,Java,Hashmap,Treemap,java文档这样说: 抛出: ClassCastException-如果键的类型不适合此映射(可选) NullPointerException-如果指定的键为null且此映射不允许使用null键(可选) 我确信这个决定有很好的理由,但我不知道确切的原因是什么。containsKey是否应该始终返回false,即使映射不允许将null值作为键?如果映射不允许键,Null显然不是映射中的键,因此我认为它应该返回false 我想到的一个特别的例子是树形图。出于性能原因,我将一个映射实例从HashMap

java文档这样说:

抛出:

ClassCastException-如果键的类型不适合此映射(可选)

NullPointerException-如果指定的键为null且此映射不允许使用null键(可选)

我确信这个决定有很好的理由,但我不知道确切的原因是什么。containsKey是否应该始终返回false,即使映射不允许将null值作为键?如果映射不允许键,Null显然不是映射中的键,因此我认为它应该返回false

我想到的一个特别的例子是树形图。出于性能原因,我将一个映射实例从HashMap更改为TreeMap,这导致了一个微妙的错误,因为containsKey for HashMap不会引发空指针异常。我觉得这种类型的更改没有很好的理由导致这样一个微妙的错误


编辑我知道NullPointerException的(可选)性质。在我看来,它的可选性并不能解释为什么它是被允许的

为了解释我为什么对此感到困惑,containsKey本质上是通过相等来比较键。在某种程度上,我来自一个功能背景,其中(比如说,Haskell)containsKey的等价物将有一个等式约束,并且对所有可以通过等式进行比较的值都有效

警察说

对于任何非空引用值x,x.equals(null)应返回false


因此,如果映射不支持空键,我认为containsKey必须返回false,因为实现“应该”相当于使用k.equals(输入)迭代键。

我认为这是一个设计决策

有两种可能的情况:

1。在支持空键的映射上使用containsKey。作为此函数的用户,您希望得到true或false。绝对没有理由返回nullPointerException,因为我们将“null”视为任何其他键,因此,如果“someKey”不存在,您可能会期望它返回false,对于null也是如此


2。在不支持空键的映射上使用containsKey。返回false将是一个奇怪的决定,因为它不是一个可能的键。此外,用户可能有对某个对象的引用,他试图将该对象用作密钥。用户希望获取NullPointerException,以便他能够意识到这种情况,尝试获取/设置null对象作为键。

您的参数均匀地应用于
ClassCastException
:即使映射不允许该类型的值作为键,containsKey不应该总是返回false吗

答:API(有文档记录的行为)不强制实现检查给定密钥的有效性,而是允许对无效的键值引发异常

请注意,这两个异常都列为“(可选)”,这意味着是否这样做取决于实现。实现可以选择简单地返回
false
,也可以选择抛出其中一个异常

实现可能会选择不为此添加特殊逻辑,所以如果算法“本机”抛出异常,那么就这样吧。API被记录为允许此默认行为

例如,
TreeMap
将本机抛出
ClassCastException
,因为在尝试调用
compare()
/
compareTo()
时会发生这种情况
HashMap
不关心对象类型,因此永远不会抛出
ClassCastException


如果给定的键为null,则不支持null键的映射的
containsKey()
逻辑可能会或可能不会执行引发
NullPointerException
,这是一个有效的结果,因此逻辑不必浪费额外的代码来处理它。

首先,
containsKey(对象键)
Map
接口中声明,因此,它取决于实现

如果问题是为什么实现不在内部执行所有这些(null,cast)检查,为什么它们抛出异常,我认为这是因为设计,并且有一个很好的理由,可以清楚地告诉输出出了什么问题

请注意:

  • 您的密钥可以是已定义密钥类型的子类型,因此可能需要强制转换。由于它是内部浇铸的,所以运行时不知道您将提供什么作为参数。因此,扔这个很好
  • 您的密钥可能是显式的,也可能是在幕后(例如,在多线程场景中)get
    null
    ,这也是处理此场景的原因
  • 请注意,这两个异常都被定义为可选的异常,因此,实现不一定要抛出它们