我已经研究了在Java 8中编写基于流的代码,并注意到了一种模式,即我经常有一个列表,但需要通过对每个元素应用一个简单的映射将其转换为另一个列表.写完.stream().map(...).collect(Collections.toList())又一次,我记得我们已经List.forEach找到了,List.map但显然这个默认方法还没有添加.
为什么List.map()(EDIT:或List.transform()or List.mumble())没有添加(这是一个历史问题),是否有一个简单的速记使用默认运行时库中的其他方法做同样的事情,我没有注意到?
正如 \xe2\x80\x9c 中所解释的,为什么 java.util.Collection 不实现新的 Stream 接口?\xe2\x80\x9d 分离 API 的设计决策,Collection并且StreamAPI 是为了分离eager和lazy 的操作。
对此,Collection API 中添加了几个批量操作:
\n\nList.replaceAll(UnaryOperator)List.sort(Comparator)Map.replaceAll(BiFunction)Collection.removeIf(Predicate)Map.forEach(BiConsumer)Iterable.forEach(Consumer)所有这些急切方法的共同点是,计算结果的函数用于修改底层集合。map返回一个新的Iterable或Collection将\xe2\x80\x99t 不适合该方案。
此外,在这些方法中,forEach(Consumer)这是唯一一个恰好具有与方法匹配的签名的Stream方法。不幸的是,因为这些方法甚至不执行相同的操作;最接近的等价物Iterable.forEach(Consumer)是Stream.forEachOrdered(Consumer). 但同样清楚的是,为什么会出现功能重叠。
对每个元素执行其副作用的操作是唯一不会修改源集合的批量操作,因此也可以由 Stream API 提供(作为终端操作)。在那里,它将在一个或多个延迟评估的中间操作之后被链接起来;使用它而无需预先执行中间操作是一种特殊情况。
\n\n由于map\xe2\x80\x99 不是终端操作,因此它根本不适合 Collection 方法的方案。最接近的等效项是List.replaceAll(UnaryOperator).
当然,我无法深入研究Java设计人员的负责人,但我可以想到一些不在map集合中包含(或其他流方法)的原因.
它的API膨胀.以更一般的方式,可以使用流进行较小的类型开销来完成相同的事情.
它会导致代码膨胀.如果我调用map了一个列表,我希望结果与我调用它的列表具有相同的运行时类型(或至少与运行时属性相同).因此,对于a ArrayList.map将返回a ArrayList,LinkedList.mapa LinkedList等.这意味着需要在所有List实现中实现相同的功能(在接口中具有合适的默认实现,因此旧代码不会被破坏).
它会鼓励代码list.map(function1).map(function2),list.stream().map(function1).map(function2).collect(Collectors.toList())因为前者构造一个立即丢弃的辅助列表,效率相当低,而后者将这两个函数应用于列表元素,然后才构造结果列表.
对于像Scala这样的函数式语言,优缺点之间的平衡可能会有所不同.
我不知道Java标准库中的快捷方式,但您当然可以实现自己的:
public static <S,T> List<T> mapList(List<S> list, Function<S,T> function) {
return list.stream().map(function).collect(Collectors.toList());
}
Run Code Online (Sandbox Code Playgroud)