2014-02-06 42 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 –