2017-03-02 11 views
1

foreachループ内で一連の列挙型値(序数とは異なる)を集計しています。複数のenum値を一緒に 'OR'するJava-8ストリーム表現

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

ストリームAPIでこれを行う方法はありますか?

私はStream<TestEnum>にこのようなラムダを使用する場合:

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

私は「ラムダで使用される変数は、効果的に、最終的なあるべき」、言うコンパイル時エラーが発生します。あなたはこのような列挙型のreduceストリームにNEDD

答えて

3

述語がブール値関数を表し、あなたが列挙値の集計束にストリームする方法を減らす使用する必要があります。

私たちは、あなたが名前のSetOfEnumsとしてHashSetのを持っていることを考慮すれば:

//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)'の形式でIDを提供することができます。または、代わりに 'get'の代わりに' orElse(0) 'を実行してください。 – Eugene

+1

@Eugeneはい、空のSetofEnumsのようなsitutationの方が良いでしょう。 –

1

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

など、EnumSet.allOf(TestEnum.class)はあなたにすべての列挙値のセットを与え、空Setを作成します。私はそれが '(acc、value) - > acc |価値 ' – Eugene

+0

あなたは正しく、私の悪いです。 – MBec

1

私は勧告が削減を使用したいが、それは良いアイデアである理由おそらくより完全な答えは説明します。

ラムダ式では、ラムダ式が定義されている範囲にある変数outputを参照できますが、値を変更することはできません。その理由は、内部的にコンパイラはラムダを本体として持つ新しい関数を作成することでラムダを実装できるようにする必要があります。コンパイラは、この生成された関数で使用されるすべての値がパラメータリストで使用できるように、必要に応じてパラメータを追加することを選択できます。あなたのケースでは、そのような関数はラムダの明示的なパラメータtestEnumを持っているはずですが、ラムダ本体のローカル変数outputも参照するため、生成された関数の2番目のパラメータとして追加することができます。実際には、コンパイラはラムダからこの関数を生成することがあります:

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

あなたが見ることができるように、outputパラメータは、発信者が使用するoutput変数のコピーである、とOR操作はコピーのみに適用されます。元のoutput変数は変更されないため、言語設計者は暗黙的にラムダに渡された値の変更を禁止することに決めました。

は削減の使用がはるかに優れたアプローチである瞬間のためともかく、最も直接的な方法で問題を回避するには、ラッパーで output変数(サイズ1または例えば int[]配列をラップすることができ AtomicInteger。ラッパーの参照は生成された関数に渡され、 outputの内容を更新するので、 outputの値ではなく、 outputが実質的にfinalのままであるため、コンパイラーは不平を言わない。たとえば、次のように

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

か、我々はのAtomicIntegerを使用していることから、我々としても今、私たちは「はあなたが後で

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

Stream並列に使用することを選択した場合には、それはスレッドセーフにすることあなたが尋ねたことに最も似ている答えを見て、還元を使用する優れた解決策について話すことができます。

は、ステートレス削減(reduce()、およびステートフル削減(collect()が)。、、違いを視覚化ハンバーガーを提供するコンベアベルトを考慮することStreamによって提供される削減の2種類があり、あなたの目標は、にハンバーガーパティのすべてを収集することですステートフル・リダクションでは、新しいハンバーガー・バンで始まり、各ハンバーガーからパティを取り出して収集し、それを収集するためにセットアップしたハンバーガー・バンの中のパティのスタックに加えます。ステートレスリダクションでは、空のハンバーガーパン(「アイデンティティ」と呼ばれます。その空のハンバーガーパンは、コンベアベルトが空の場合に終わるものですから)、各ハンバーガーがベルトに着くと、以前に蓄積したバーガーのコピーを取り出し、新しいものからパテを追加します。以前に蓄積されたハンバーガーを捨てる。

ステートレスリダクションは膨大な廃棄物のように見えるかもしれませんが、累積値をコピーするのが非常に安い場合があります。そのようなケースの1つは、プリミティブ型のコピーが非常に安価であるため、加算、ORingなどのアプリケーションでプリミティブを処理するときにステートレスな縮小が理想的です。

ステートレス縮小を使用すると、 :

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

熟考するいくつかのポイント:forループ

  • あなたの元には、あなたのセットが非常に大きく、あなたが並列ストリームを使用し、おそらく場合を除いて、ストリームを使用するよりも、おそらく高速です。ストリームを使用するためにストリームを使用しないでください。彼らが理にかなっているならば、それを使う。
  • 私の最初の例では、私はStream.forEach()の使用を示しました。 Streamを作成してforEach()に電話するだけであれば、コレクションのforEach()に直接電話する方が効率的です。
  • あなたはSetの種類について言及していませんでしたが、EnumSet<TestEnum>を使用していることを願っています。これはビットフィールドとして実装されているため、他の種類のSetよりもはるかに優れています(O(1))。 EnumSet.noneOf(TestEnum.class)は不変の減少だが、あなたは `acc`(int型であることを)変更される
関連する問題