2015-04-01 84 views
9

虽然新的Java 8 Stream API我开始纳闷,为什么不打转转:为什么地图<K,V>不能扩展功能<K,V>?

public interface Map<K,V> extends Function<K, V> 

甚至:

public interface Map<K,V> extends Function<K, V>, Predicate<K> 

这将是相当容易的Mapdefault方法来实现interface

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

@Override default V apply(K k) { 
    return get(k); 
} 

而且它将允许使用Mapmap方法:

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); 

或者在一个filter方法作为Predicate

我发现一个Map我的用例中的很大一部分正好是构造或类似构造 - 作为重新映射/查找Function

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

回答

12

的JDK团队当然知道作为一个映射函数java.util.Map作为数据结构和java.util.function.Function之间的数学关系。毕竟,在早期的JDK 8 prototype builds中,Function被命名为Mapper。调用每个流元素上的函数的流操作称为Stream.map

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

主要问题是,如果java.util.Mapjava.util.function.Function的子类型将会获得什么? comments关于子类型是否意味着“是-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)) 
+2

我认为关于'map :: get'中烘焙的观点,而不是'getOrDefault'或'computeIfAbsent'或者其他任何如果迄今为止最引人注目的论证。我可以看到'null'会成为一个问题,尤其是'Stream'会像'NONNULL'一样开始。感谢您对设计决策的宝贵意见! – 2015-04-01 16:43:08

4

因为地图不是功能。继承是A是B的关系。不适用于A可以是各种B关系的主题。

为了有一个功能转换的关键它的价值,你只需要

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

为了有一个谓词测试一个对象是否包含在地图上,你只需要

Predicate<Object> p = map::contains; 

那比您的建议更清晰和更具可读性。

+1

Scala中闲逛可见其['Map'不混入一个'PartialFunction'](http://www.scala-lang.org/api/2.11.4/index.html#scala.collection .MapLike)。我想这只是一个不同的设计决定,但它表明,也许它不是那么清晰...... – 2015-04-01 10:41:38

+2

地图如何不是一个功能?我认为将Map定义为一个函数是完全正确的,例如,如果x不在域中,则映射中由键定义的域上的f(k)= v'和'f(x)= null'。 – user2336315 2015-04-01 10:48:48

+0

@ user2336315地图不是数学意义上的函数。使用相同密钥的多个呼叫可能会产生不同的结果。 – zeroflagL 2015-04-01 11:03:10

8

的问题是“为什么要延长Function?”

您使用strings.map(map)并不真正说明改变的类型继承(暗示添加方法的Map接口)的想法,例如给予差别不大到strings.map(map::get)。并不清楚使用Map作为Function是否真的很常见,以至于它应该得到特殊的治疗,使用map::remove作为Function或使用的map::get作为ToIntFunctionmap::getMap<T,T>作为BinaryOperator

这在Predicate的情况下更有问题;应该map::containsKey真的得到特殊待遇相比map::containsValue

这也值得注意的方法的类型签名。Map.getObject → V功能的签名,而你建议Map<K,V>应该(或只是通过查看型)延长Function<K,V>这是从地图的概念图可以理解的,但它表明,有两个相互矛盾的预期,这取决于你是否看该方法或类型。最好的解决方案不是修复功能类型。然后,你可以指定map::get要么Function<Object,V>Function<K,V>大家都快乐......

+0

我认为这是一个比[JB Nizet的答案](http://stackoverflow.com/a/29388651/2071828)更好的说法,我可以在这里看到逻辑。关键的一点似乎是'Map extends Function '会导致Java原语('ToXXXFunction')的问题,并且更重要的是,不能很好地处理(get的)(有点冒险的)签名 - 那些主要考虑?在这种情况下的基本答案似乎是“遗留问题”...... – 2015-04-01 12:55:44

+1

@蜘蛛蜘蛛:我无法看到开发人员的头脑。也许他们实际上从来没有想过这件事。特别是因为根本没有试图将现有类型改装成功能,例如,让'Comparator'扩展'ToIntBiFunction'等。也许更简单的答案是函数和其他类型通常被认为是不同的。使用'Map'扩展'Function'意味着您可以创建像调用'firstMap.compose(otherMap)'这样的宝石... ... – Holger 2015-04-01 13:06:08

+5

指责它“遗留”的问题太简单了。 Holger说得对;正确的问题是“为什么要这样”,而不是“为什么不这样”。并没有令人信服的理由,特别是考虑将map :: get或list :: contains转换为Function或Predicate有多简单。 – 2015-04-01 15:20:06

相关问题