2014-02-06 40 views
14

我最近开始玩Java 8,在Haskell/Scala之前完成了一些小部分。我正在尝试使用Java中的高阶函数,例如mapforEach,我正在努力去理解将所有东西推向意识形态的动机。我理解它给好的,一般的抽象,它被认为是懒惰的,但是让我们考虑一个非常简单的,常见的例子:Java 8 - 流思想

list.map(x -> do_sth(x)); 

很常见的成语,期待这个返回一个List<T>。现在,在Java 8,我需要做这样的某物:

list.stream().map(x -> doSth(x)).collect(Collectors.toList()) 

现在,就我看到这一点,流将不适用的地图,直到收集被调用,所以会有一次通过集合在引擎盖下。我看不到的是为什么这些常见的地图用例,如map.toList(),list.groupBy()等列表不会被添加到相应的接口?我在这里错过了一个潜在的设计决定吗?

回答

24

一些新的方法已被直接添加到各种集合,这些集合在这些集合上进行可变操作。例如,要在列表的每个元素上运行函数,用返回值替换原始元素,请使用List.replaceAll(UnaryOperator)。其他例子是Collection.removeIf(Predicate)List.sort()Map.replaceAll(BiFunction)

相比之下,Stream中添加了一些新的方法,例如过滤器,映射,跳过,限制,排序,截然不同等等。其中大多数是懒惰的,它们不会改变源代码,而是将元素传递给下游。我们确实考虑直接将这些添加到集合类。此时出现了几个问题。我们如何区分急切的变化操作和懒惰的流式生成操作?重载是困难的,因为它们具有不同的返回类型,所以它们必须具有不同的名称。这些操作如何链接?急切的操作将不得不生成集合来存储中间结果,这可能相当昂贵。由此产生的收集API将会产生渴望,变化和懒惰,非变异方法的混淆。

第二顺序的考虑是潜在的不兼容性与添加默认方法。向接口添加默认方法的最大风险是在该接口的实现上与现有方法的名称冲突。如果具有相同名称和参数的方法(通常不带参数)具有不同的返回类型,那是不可避免的不兼容性。出于这个原因,我们一直不太情愿添加大量的默认方法。

由于这些原因,我们决定在流API中保留所有惰性的非变异方法,代价是需要额外的方法调用stream()和collect()以在集合和流之间进行桥接。对于一些常见的情况,我们添加了渴望,直接将调用突变为集合接口,例如上面列出的接口。

请参阅lambdafaq.org进一步讨论。

+1

什么我不明白,也许你可以解释 - 为什么这,.collect(Collectors.toList()),当它可以有一个toList()方法内置?它对我来说似乎冗长而笨拙。 –

+5

如果你有'toList',你可能会想'toSet'和'toMap'。但是你仍然需要'collect'方法,因为它非常灵活。所以现在一些收集操作会有自己专用的便利方法,而对于其他收集操作你需要调用'collect'。也许这种便利性值得不妥之处和不一致,但设计师认为不是。 –

+0

我看到很多代码被迫写入:stream()... collect(Collectors。toList()),而不是stream()... toList(),因为jdk设计器的一些愚蠢的想法。那真令人恶心。 “toList”,“toSet”,“toMap”是最常用的终止操作。他们应该被添加到Stream API –