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

时间:2017-02-21 10:28:09

标签: java java-8

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

为什么List.map()(编辑:或List.transform()List.mumble())  没有添加(这是一个历史问题),是否有一个简单的速记使用默认运行时库中的其他方法做同样的事情,我没有注意到?

4 个答案:

答案 0 :(得分:5)

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

  1. 这是API膨胀。同样的事情可以用更通用的方式完成,使用流来进行较小的打字开销。

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

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

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

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

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

答案 1 :(得分:4)

正如“Why doesn't java.util.Collection implement the new Stream interface?”中所述,分离Collection API和Stream API的设计决策是将渴望懒惰分开操作。

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

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

所有这些 eager 方法的共同点是,评估结果的函数用于修改基础Collection。返回新mapIterable的{​​{1}}方法不适合该方案。

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

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

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

答案 2 :(得分:1)

从概念的角度来看,forEach方法(在java.lang.Iterable接口中声明的上述评论中曾多次说过)和map()方法非常不同。

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

java.util.stream.Stream#map(...)返回<R> Stream<R>

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

我相信这就是原因。

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

    default <A, B> Iterable<B> map(Function<A, B> f) {
        //...
    } 

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

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

答案 3 :(得分:0)

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

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