为什么Map <k,v>不扩展函数<k,v>?</k,v> </k,v>

时间:2015-04-01 10:34:34

标签: java-8

在使用新的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

3 个答案:

答案 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.Mapjava.util.function.Function的子类型会得到什么?在comments中有一些关于子类型是否意味着“是一个”关系的讨论。子类型不是关于对象的“is-a”关系 - 因为我们讨论的是接口而不是类 - 但它确实意味着可替代性。因此,如果MapFunction的子类型,则可以执行此操作:

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> ToIntFunctionmap::get作为Map<T,T> {{1} }}

BinaryOperator的情况下,这更令人质疑;与Predicate相比,map::containsKey真的应该得到特殊待遇吗?

值得注意的是方法的类型签名。 map::containsValue具有Map.get的功能签名,而您建议Object → V应该扩展Map<K,V>,这可以从地图的概念视图中理解(或者只是通过查看类型),但它表明存在两个相互矛盾的期望,这取决于您是查看方法还是类型。最好的解决方案是不修复功能类型。然后,您可以将Function<K,V>分配给map::getFunction<Object,V>,大家都很高兴...

答案 2 :(得分:4)

因为Map不是函数。继承是针对 A是B 的关系。不适用于 A可以成为各种B 关系的主题。

要使一个函数将一个键转换为它的值,你只需要

Function<K, V> f = map::get;

要让谓词测试对象是否包含在地图中,您只需要

Predicate<Object> p = map::contains;

这比您的提案更清晰,更易读。