2017-03-02 66 views
1

我在foreach循環中聚集了一堆枚舉值(與序數值不同)。Java-8流表達式將'OR'幾個枚舉值聚合在一起

int output = 0; 
    for (TestEnum testEnum: setOfEnums) { 
     output |= testEnum.getValue(); 
    } 

有沒有辦法在Stream API中做到這一點?

如果我用這樣的拉姆達在Stream<TestEnum>

setOfEnums.stream().forEach(testEnum -> (output |= testEnum.getValue()); 

我得到的說,「拉姆達使用變量應該是有效的最終」編譯時錯誤。

回答

3

謂語表示布爾值函數,你需要使用減少流的方法來聚集一堆枚舉值。

如果我們認爲你有HashSet的爲名爲SetOfEnums:

//int initialValue = 0; //this is effectively final for next stream pipeline if you wont modify this value in that stream 
    final int initialValue = 0;//final 
    int output = SetOfEnums.stream() 
        .map(TestEnum::getValue) 
        .reduce(initialValue, (e1,e2)-> e1|e2); 
+1

您可以使用'.reduce(0,(e1,e2) - > e1 | e2)'的形式提供身份。或者,而不是'get'做一個'orElse(0)' – Eugene

+1

@Eugene是的,這將是更好的情況下像空Setofenums否則可選將拋出異常,我會修改它。 –

1

您NEDD來枚舉reduce流是這樣的:

int output = Arrays.stream(TestEnum.values()).mapToInt(TestEnum::getValue).reduce(0, (acc, value) -> acc | value); 
+1

這是一個不可改變的減少,但你改變'acc'(即一個int)。我認爲它應該是'(acc,value) - > acc |價值' – Eugene

+0

你是對的,我的壞。 – MBec

1

我喜歡的建議減量使用,但也許一個更完整的答案會說明爲什麼這是一個好主意。

在lambda表達式中,您可以引用變量,如output,它們在定義lambda表達式的範圍內,但不能修改這些值。原因在於,在內部,編譯器必須能夠通過創建一個以lambda作爲其主體的新函數來實現您的lambda,如果它選擇這樣做的話。編譯器可以根據需要選擇添加參數,以便在生成的函數中使用的所有值在參數列表中可用。在你的情況下,這樣的函數肯定會有lambda的顯式參數testEnum,但是因爲你也可以在lambda體中引用局部變量output,所以它可以將其作爲第二個參數添加到生成的函數中。實際上,編譯器可能會產生從拉姆達此功能:

private void generatedFunction1(TestEnum testEnum, int output) { 
    output |= testEnum.getValue(); 
} 

正如你所看到的,output參數是由主叫方使用的output變量的副本,或運算將只適用於複製。由於原來的output變量不會被修改,因此語言設計者決定禁止修改隱式傳遞給lambda的值。

要解決的最直接的方式問題,就目前的使用量減少的是一個更好的辦法,撇開,你可以換一個包裝的output變量(例如int[]數組大小1或中AtomicInteger。包裝的引用將按值傳遞給生成的函數,並且由於您現在將更新output的內容,因此output,output的值仍然有效,因此編譯器不會抱怨。例如:

AtomicInteger output = new AtomicInteger(); 
setOfEnums.stream().forEach(testEnum -> (output.set(output.get() | testEnum.getValue())); 

或者,因爲我們使用的AtomicInteger,我們不妨讓它線程安全的情況下,以後你選擇使用並行Stream

AtomicInteger output = new AtomicInteger(); 
setOfEnums.stream().forEach(testEnum -> (output.getAndUpdate(prev -> prev | testEnum.getValue()))); 

現在我們」我已經回答了一個最類似於你所問的答案,我們可以談論使用減少的優越解決方案,其他答案已經被推薦。

有兩種通過Stream提供的減少,無國籍減少(reduce()和狀態還原(collect())。爲了顯現差異,考慮傳送帶提供漢堡包,和你的目標是收集所有的漢堡肉餅成一個大的漢堡包,有了狀態的減少,你會從一個新的漢堡麪包開始,然後在每個漢堡包到達時收集這些餅乾,然後將它添加到你設置的漢堡麪包堆中以收集它們。在無狀態的減少中,你首先用一個空的漢堡麪包(稱爲「身份」,因爲如果輸送帶是空的,那麼空漢堡麪包就是最終的結果),並且當每個漢堡包到達帶上時,以前累積的漢堡的副本,並添加新的餅乾,只是一個激發了,丟棄了以前積累的漢堡。

無狀態減少可能看起來像一個巨大的浪費,但有些情況下,複製累計值非常便宜。其中一種情況是積累原始類型 - 原始類型的拷貝非常便宜,因此在處理諸如求和,ORing等應用程序中的基元時,無狀態簡化是理想的。因此,使用無狀態約簡,您的示例可能會變成:

setOfEnums.stream() 
    .mapToInt(TestEnum::getValue) // or .mapToInt(testEnum -> testEnum.getValue()) 
    .reduce(0, (resultSoFar, testEnum) -> resultSoFar | testEnum); 

的幾點思考:

  • 您的循環原來可能比使用流得更快,但也許如果你的設置是非常大的,你使用並行流。爲了使用流,請勿使用流。如果它們有意義,請使用它們。
  • 在我的第一個例子中,我展示了使用Stream.forEach()。如果您發現自己創建了Stream並且只需撥打forEach(),則直接在集合上調用forEach()效率更高。
  • 你沒有提及你正在使用什麼樣的Set,但我希望你使用的是EnumSet<TestEnum>。因爲它是作爲一個位域來實現的,所以對於所有的操作,甚至複製,它都比其他類型的Set好得多(O(1))。 EnumSet.noneOf(TestEnum.class)創建一個空SetEnumSet.allOf(TestEnum.class)爲您提供了一組所有枚舉值等