为什么Map <K,V>没有扩展函数<K,V>?

Bor*_*der 12 java-8

在使用新的Java 8 StreamAPI时,我不禁要问,为什么不呢:

public interface Map<K,V> extends Function<K, V>
Run Code Online (Sandbox Code Playgroud)

甚至:

public interface Map<K,V> extends Function<K, V>, Predicate<K>
Run Code Online (Sandbox Code Playgroud)

使用以下default方法实现相当容易Map interface:

@Override default boolean test(K k) {
    return containsKey(k);
}

@Override default V apply(K k) {
    return get(k);
}
Run Code Online (Sandbox Code Playgroud)

并且它允许Mapmap方法中使用a :

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);
Run Code Online (Sandbox Code Playgroud)

或者作为Predicate一种filter方法.

我发现a的一小部分用例Map正好是那个构造或类似构造 - 作为重映射/查找Function.

那么,为什么JDK设计者Map在重新设计Java 8时没有决定添加这个功能呢?

Stu*_*rks 14

JDK团队当然知道java.util.Map作为数据结构和java.util.function.Function映射函数之间的数学关系.毕竟,在早期的JDK 8 原型版本中Function命名.并且调用在每个流元素上调用函数的流操作.MapperStream.map

甚至还有一个关于可能重命名Stream.map为其他内容的讨论,例如transform因为转换函数和Map数据结构之间可能存在混淆.(抱歉,找不到链接.)此提案被拒绝,理由是概念上的相似性(map为此目的是常用的).

主要问题是,如果java.util.Map是子类型会得到什么java.util.function.Function?关于子类型是否暗示"is-a"关系的评论中有一些讨论.子类型不是关于对象的"is-a"关系 - 因为我们讨论的是接口而不是类 - 但它确实意味着可替代性.因此,如果Map是子类型Function,则可以执行此操作:

Map<K,V> m = ... ;
source.stream().map(m).collect(...);
Run Code Online (Sandbox Code Playgroud)

我们现在正面临着Function.apply现有Map方法之一的烘焙行为.可能唯一明智的是Map.get,如果密钥不存在则返回null.坦率地说,这些语义有点糟糕.真正的应用程序可能必须编写自己的方法来提供密钥丢失策略,所以似乎没有什么优势可以编写

map(m)
Run Code Online (Sandbox Code Playgroud)

代替

map(m::get)
Run Code Online (Sandbox Code Playgroud)

要么

map(x -> m.getOrDefault(x, def))
Run Code Online (Sandbox Code Playgroud)

  • 我认为关于在`map :: get`而不是`getOrDefault`或`computeIfAbsent`或其他任何其他方面进行烘焙的观点,如果到目前为止最引人注目的论点.我可以看到`null`将是一个问题,特别是因为许多`Stream`将以'NONNULL`开始.感谢您对设计决策的宝贵见解! (2认同)

Hol*_*ger 8

问题是"为什么要扩展Function?"

你使用的例子strings.map(map)并没有真正证明改变类型继承的想法(暗示为Map接口添加方法),因为它没有什么区别strings.map(map::get).而且目前尚不清楚是否一个使用MapFunction是真的常见的,它应该比获得特殊待遇,例如使用map::remove一个Function或使用map::get一个的Map<…,Integer>作为ToIntFunctionmap::getMap<T,T>作为BinaryOperator.

在a的情况下,这更令人质疑Predicate; map::containsKey真的应该得到一个特殊的待遇比较map::containsValue

值得注意的是方法的类型签名.Map.get有一个功能签名Object ? V虽然你建议Map<K,V>应该扩展Function<K,V>,这可以从地图的概念视图(或只是通过查看类型)可以理解,但它表明有两个相互矛盾的期望,取决于你是否看到方法或在类型.最好的解决方案是不修复功能类型.然后你可以分配map::get给任何一个Function<Object,V>或者Function<K,V>每个人都很高兴...

  • 把它归咎于"遗留"问题太容易了.霍尔格是对的; 正确的问题是"为什么要这样",而不是"为什么不呢".并且没有令人信服的理由,特别是考虑到将map :: get或list :: contains转换为Function或Predicate是多么简单. (5认同)
  • @BoristheSpider我认为布莱恩和我基本上说同样的话,除了我写了更多的话.:-) (3认同)

JB *_*zet 5

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

要将键转换为其值的函数,您只需要

Function<K, V> f = map::get;
Run Code Online (Sandbox Code Playgroud)

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

Predicate<Object> p = map::contains;
Run Code Online (Sandbox Code Playgroud)

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

  • 地图怎么不是函数?我认为将 Map 定义为一个函数是完全没问题的,如果 x 不在域中,则在由映射中的键定义的域上定义 `f(k) = v` 和 `f(x) = null`。 (2认同)
  • @zeroflagL 但是我们可以认为这是一个带有新图像集的新函数。在时间 t,它真的表现得像一个函数: (2认同)