为什么`List`在有`forEach`时没有`map`默认方法?

我已经研究过在Java 8中编写基于流的代码,并注意到了一种模式,即我经常有一个列表,但需要通过对每个元素应用一个简单的映射将其转换为另一个列表。 写完.stream().map(...).collect(Collections.toList())我还记得我们有List.forEach所以我找了List.map但显然这个默认方法还没有添加。

为什么没有添加List.map() (EDIT:或List.transform()List.mumble() )(这是历史问题),并且使用默认运行时库中的其他方法执行相同的简单速记我刚才没注意到的事情?

当然,我无法深入了解Java设计人员的负责人,但我可以想到一些不在集合中包含map (或其他流方法)的原因。

  1. 它的API膨胀。 以更一般的方式,可以使用流进行较小的打字开销,同样的事情。

  2. 它导致代码膨胀。 如果我在列表上调用map ,我希望结果与我调用它的列表具有相同的运行时类型(或至少与运行时属性相同)。 因此,对于ArrayList.map将返回一个ArrayListLinkedList.map一个LinkedList等。这意味着需要在所有List实现中实现相同的function(在接口中使用合适的默认实现,因此旧代码不会被破坏) 。

  3. 它会鼓励像list.map(function1).map(function2)这样的代码,它比list.stream().map(function1).map(function2).collect(Collectors.toList())效率低list.stream().map(function1).map(function2).collect(Collectors.toList())因为前者构造一个立即丢弃的辅助列表,而后者将这两个函数应用于列表元素,然后构造结果列表。

对于像Scala这样的函数式语言,优缺点之间的平衡可能会有所不同。

我不知道Java标准库中的快捷方式,但您当然可以实现自己的:

 public static  List mapList(List list, Function function) { return list.stream().map(function).collect(Collectors.toList()); } 

正如“ 为什么java.util.Collection不实现新的Stream接口? “分离Collection API和Stream API的设计决策是为了分离渴望懒惰的操作。

在这方面,Collection API中添加了几个批量操作:

  • List.replaceAll(UnaryOperator)
  • List.sort(Comparator)
  • Map.replaceAll(BiFunction)
  • Collection.removeIf(Predicate)
  • Map.forEach(BiConsumer)
  • Iterable.forEach(Consumer)

所有这些热切方法的共同点是,对结果求值的函数用于修改基础Collection。 返回新IterableCollection map方法不适合该方案。

此外,在这些方法中, forEach(Consumer)是唯一恰好具有与Stream方法匹配的签名的方法。 这是不幸的,因为这些方法甚至没有做同样的事情; 与Iterable.forEach(Consumer)最接近的是Stream.forEachOrdered(Consumer) 。 但同样清楚的是,为什么存在function重叠。

对每个元素执行副作用的操作是唯一不修改源集合的批量操作,因此也可以由Stream API提供(作为终端操作)。 在那里,它将在一个或多个懒惰评估的中间操作之后被链接; 使用它而不进行前置中间操作,是一种特殊情况。

由于map不是终端操作,因此它根本不适合Collection方案的方案。 最接近的等价物是List.replaceAll(UnaryOperator)

从概念上看, forEach方法(在java.lang.Iterable接口中声明的上述注释中有几次表示)和map()方法有很大的不同。

主要区别在于java.lang.Iterable#forEach(...)什么都不返回,它是void 。 因此,将其添加到具有默认实现的Iterable接口不会破坏任何内容,并且很好地适应此结构的逻辑。

java.util.stream.Stream#map(...)返回 Stream

如果我是Iterable接口的开发者并且会被要求在那里添加map ,我首先会问:它应该返回什么类型? 如果它是 Stream ,那么Iterable不适合它。 如果没有 – 还有什么?

我相信这就是原因。

UPD :@DavidtenHove建议,为什么不让这个map方法返回Iterable ,如:

  default  Iterable map(Function f) { //... } 

我的意见:因为在这种情况下, Iterable接口变成了Stream接口的模拟,没有任何意义。

forEach还复制了Stream的逻辑,但它在Iterable逻辑中非常适合。

简单地说:他们不想在所有流媒体类中将所有内容都放在Stream类中。 他们只会变得太大。 他们把它放在每个因为它是如此常用,但在那里画线。

至于短线,我不相信有任何。 您只需将collect()方法与收集器一起使用即可。