在Java 9中,为List
、Set
和Map
接口引入了新的工厂方法。这些方法允许在一行中使用值快速实例化 Map 对象。现在,如果我们考虑:
Map<Integer, String> map1 = new HashMap<Integer, String>(Map.of(1, "value1", 2, "value2", 3, "value3"));
map1.put(4, null);
上述情况是允许的,没有任何例外,而如果我们这样做:
Map<Integer, String> map2 = Map.of(1, "value1", 2, "value2", 3, "value3", 4, null );
它抛出:
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:221)
..
我无法得到,为什么在第二种情况下不允许空。
我知道 HashMap 可以将 null 作为键和值,但为什么在 Map.of 的情况下受到限制?
同样的事情发生在java.util.Set.of("v1", "v2", null)
和java.util.List.of("v1", "v2", null)
的情况下。
正如其他人指出的那样,Map
合约允许拒绝空值...
[S]ome 实现禁止
null
键和值 [...]。尝试插入不合格的键或值会引发未经检查的异常,通常是NullPointerException
或ClassCastException
。
。收集工厂(不仅仅是在地图上)利用了这一点。
它们不允许
null
键和值。尝试使用null
键或值创建它们会导致NullPointerException
。
但是为什么?
现在,允许在集合中null
被视为设计错误。这有多种原因。一个好的是可用性,其中最突出的麻烦制造者是Map::get
.如果它返回null
,则不清楚是缺少键还是null
值。一般来说,保证null
免费的集合更易于使用。在实现方面,它们还需要较少的特殊大小写,使代码更易于维护和提高性能。
你可以听Stuart Marks在这个演讲中解释它,但JEP 269(介绍工厂方法的那个)也总结了它:
将不允许使用空元素、键和值。(最近引入的集合不支持 null。此外,禁止空值为更紧凑的内部表示、更快的访问和更少的特殊情况提供了机会。
由于HashMap
在慢慢发现时已经在野外,因此在不破坏现有代码的情况下更改它为时已晚,但这些接口的最新实现(例如ConcurrentHashMap
) 不再允许null
,工厂方法的新集合也不例外。
(我认为另一个原因是显式使用null
值被视为可能的实现错误,但我错了。这即将复制密钥,这也是非法的。
因此,不允许null
有一些技术原因,但也是为了使用创建的集合提高代码的健壮性。
在我看来,不可空性对键有意义,但对值没有意义。
Map
接口变成了一个弱合约,你不能不看实现就相信它的行为,这是假设你有权限看到它。Java11 的Map.of()
不允许空值,而HashMap
允许,但它们都实现了相同的Map
协定 - 怎么会呢?- 具有空值或根本没有值是不可能的,并且可以说不应该被视为一个不同的场景。当你得到一个键的值并且你得到空时,谁在乎它是否被明确放在那里?提供的键根本没有值,地图不包含这样的键,就这么简单。Null 为空,没有 null 或 null^2
- 现有的库广泛使用map作为将属性传递给它们的手段,其中许多是可选的,这使得
Map.of()
作为此类结构的构造毫无用处。 - Kotlin 在编译时强制执行可空性,并允许具有空值且没有已知问题的映射。
- 不允许空值的原因可能只是由于创建者的实现细节和便利性。
确切地说 - 允许HashMap
存储 null,而不是静态工厂方法返回的Map
。并非所有地图都相同。
一般来说,据我所知,首先允许HashMap
中的空值作为键是错误的,较新的集合首先禁止这种可能性。
想想当你的HashMap
中有一个条目具有特定的键和值 == null 时的情况。你得到,它返回null
.什么意思?它有null
的映射还是不存在?
Key
也是如此 - 来自这种空键的哈希码必须始终进行特殊处理。首先禁止空值 - 使这更容易。
虽然 HashMap 确实允许空值,但Map.of
不使用HashMap
,如果将 用作键或值,则会引发异常,如文档所示:
) 和 Map.ofEntries() 静态工厂方法提供了一种创建不可变映射的便捷方法。通过这些方法创建的 Map 实例具有以下特征:
。
它们不允许空键和值。尝试使用空键或值创建它们会导致 NullPointerException。
主要区别在于:当您构建自己的地图时,"选项 1"方式...然后你含蓄地说:"我想在我正在做的事情上拥有充分的自由"。
因此,当您决定您的地图应该具有空键或值时(可能是出于此处列出的原因),那么您可以自由地这样做。
但是"选项 2"是关于一个方便的事情 - 可能旨在用于常量。Java背后的人只是简单地决定:"当你使用这些方便的方法时,那么生成的映射应该是无空的"。
允许空值意味着
if (map.contains(key))
与
if (map.get(key) != null)
这有时可能是一个问题。或者更准确地说:在处理该地图对象时要记住这一点。
这只是一个轶事提示,为什么这似乎是一种合理的方法:我们的团队自己实现了类似的便利方法。你猜怎么着:在对未来 Java 标准方法的计划一无所知的情况下 - 我们的方法做完全相同的事情:它们返回传入数据的不可变副本;当您提供空元素时,它们会抛出。我们甚至非常严格,当您传入空列表/地图/...我们也抱怨。
文档没有说明为什么不允许null
:
它们不允许
null
键和值。尝试使用null
键或值创建它们会导致NullPointerException
。
在我看来,将产生常量的Map.of()
和Map.ofEntries()
静态工厂方法主要由编译类型的开发人员形成。那么,保留null
作为键或值的原因是什么?
而Map#put
通常通过在运行时填充可能出现null
键/值的映射来使用。
并非所有地图都允许 null 作为键
docs of Map
中提到的原因.
某些映射实现对其可能包含的键和值有限制。例如,某些实现禁止空键和值,而某些实现对其键的类型有限制。尝试插入不合格的键或值会引发未经检查的异常,通常是 NullPointerException 或 ClassCastException。