在使用新的Java 8 Stream
API时,我不禁要问,为什么不呢:
public interface Map<K,V> extends Function<K, V>
甚至:
public interface Map<K,V> extends Function<K, V>, Predicate<K>
使用default
Map
上的interface
方法实施相当容易:
@Override default boolean test(K k) {
return containsKey(k);
}
@Override default V apply(K k) {
return get(k);
}
它允许在Map
方法中使用map
:
final MyMagicMap<String, Integer> map = new MyMagicHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
map.put("D", 4);
final Stream<String> strings = Arrays.stream(new String[]{"A", "B", "C", "D"});
final Stream<Integer> remapped = strings.map(map);
或Predicate
方法中的filter
。
我发现Map
的一小部分用例正是该构造或类似构造 - 作为重新映射/查找Function
。
那么,为什么JDK设计者在重新设计Java 8期间没有决定将这个功能添加到Map
?
答案 0 :(得分:14)
JDK团队当然知道java.util.Map
作为数据结构和java.util.function.Function
作为映射函数之间的数学关系。毕竟,Function
在早期的JDK 8 prototype builds中被命名为Mapper
。并且在每个流元素上调用函数的流操作称为Stream.map
。
甚至讨论过可能将Stream.map
重命名为transform
等其他内容,因为转换函数和Map
数据结构之间可能存在混淆。 (抱歉,找不到链接。)此提案被拒绝,理由是概念上的相似性(为此目的map
是常用的)。
主要问题是,如果java.util.Map
是java.util.function.Function
的子类型会得到什么?在comments中有一些关于子类型是否意味着“是一个”关系的讨论。子类型不是关于对象的“is-a”关系 - 因为我们讨论的是接口而不是类 - 但它确实意味着可替代性。因此,如果Map
是Function
的子类型,则可以执行此操作:
Map<K,V> m = ... ;
source.stream().map(m).collect(...);
我们现在正面临着对现有Function.apply
方法中的Map
行为的烘焙。可能唯一明智的是Map.get
,如果密钥不存在则返回null。坦率地说,这些语义有点糟糕。真正的应用程序可能必须编写自己的方法来提供密钥丢失策略,所以似乎没有什么优势可以编写
map(m)
而不是
map(m::get)
或
map(x -> m.getOrDefault(x, def))
答案 1 :(得分:8)
问题是“为什么要延长Function
?”
使用strings.map(map)
的示例并不能真正证明更改类型继承的想法(意味着向Map
接口添加方法),因为与strings.map(map::get)
的区别很小。目前尚不清楚使用Map
作为Function
是否真的很常见,它应该得到特殊处理,例如使用map::remove
作为Function
或使用map::get
Map<…,Integer>
ToIntFunction
或map::get
作为Map<T,T>
{{1} }}
在BinaryOperator
的情况下,这更令人质疑;与Predicate
相比,map::containsKey
真的应该得到特殊待遇吗?
值得注意的是方法的类型签名。 map::containsValue
具有Map.get
的功能签名,而您建议Object → V
应该扩展Map<K,V>
,这可以从地图的概念视图中理解(或者只是通过查看类型),但它表明存在两个相互矛盾的期望,这取决于您是查看方法还是类型。最好的解决方案是不修复功能类型。然后,您可以将Function<K,V>
分配给map::get
或Function<Object,V>
,大家都很高兴...
答案 2 :(得分:4)
因为Map不是函数。继承是针对 A是B 的关系。不适用于 A可以成为各种B 关系的主题。
要使一个函数将一个键转换为它的值,你只需要
Function<K, V> f = map::get;
要让谓词测试对象是否包含在地图中,您只需要
Predicate<Object> p = map::contains;
这比您的提案更清晰,更易读。