67911


現在Java8の勉強をしています。  
streamでcarListの中に該当のcarIdがある場合、isExistをtrueにする処理を書きました。  
forEachではとくにbooleanでもエラーにならなかったのですが、streamではAtomicBooleanにしろと言われました。

**なぜなのでしょうか？またAtomicBooleanにするしかないのでしょうか？あまり見慣れないので進んで使っていいのかわかりません。**

アドバイスいただけると幸いです。

[code]

    // boolean isExist = false;  //エラーになる
    AtomicBoolean isExist = new AtomicBoolean(false);
            for (car c:carList) {
                if(c.carId() == this.carId){
                    isExist.set(true);
                }
            }
    
            carList.stream()
                    .filter(c -> c.getCarId() == this.carId)
                    .forEach(i -> isExist.set(true));
    
[/code]


CC BY-SA 4.0
2020-06-22T14:58:40.873
67905
2020-06-23T04:42:25.167
40231
question
java java8 stream
stream処理のラムダ式の中でbooleanの設定がエラーになるのがなぜか知りたい


> streamではAtomicBooleanにしろと言われました。

(コンパイルエラーとしては "ラムダ式から参照されるローカル変数は、finalまたは事実上のfinalである必要があります"
なので、ここから`AtomicBoolean`に至るまでには少し飛躍があるように思われますが、疑問の関心には含まれていないようなので端折ります)

* * *

簡単に言うと、ストリーム操作はマルチスレッドで実行される可能性を考慮して実装する必要があるからです。

[`Stream#forEach()`の説明](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/Stream.html#forEach\(java.util.function.Consumer\))で次のように表現されています:

> 与えられた任意の要素に対し、ライブラリが選択した任意のタイミングで任意のスレッド内でアクションが実行される可能性があります。
> アクションが共有状態にアクセスする場合、必要な同期を提供する責任はアクションにあります。

今回のコードでは、`AtomicBoolean`を使うことが「必要な同期を提供する」ことに当たります。

また、[`java.util.stream`のリファレンス](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/package-
summary.html)にも説明があります。  
特に、次の2つのセクションが今回の疑問の説明に関わってきます:

  * Parallelism
  * Side-effects

* * *

> またAtomicBooleanにするしかないのでしょうか？

前述の[Side-
effectsセクション](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/package-
summary.html#package-summary.38qnK)で例として挙げられているコードの解説

>
> 並列実行時には、`ArrayList`がスレッドセーフでないために不正な結果が生成されますし、必要な同期を追加すれば競合が発生し、並列性のメリットが薄れます。
> さらに言えば、ここで副作用を使用する必要はまったくありません。`forEach()`は単純に、より効率的で安全な、並列化により適したリダクション操作で置き換えることができます。

が今回のコードにもそのまま当てはまります(対象が`ArrayList`ではない点を除いて)。

今回のコードの場合、Stream APIを利用するのであれば

[code]

    boolean isExist = carList.stream()
        .anyMatch(c -> c.getCarId() == this.carId);
    
[/code]

とするのが妥当かと考えます。

ここで`anyMatch()`は[リファレンス](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/Stream.html#anyMatch\(java.util.function.Predicate\))にある通り短絡操作であり、for-
loopだと次のような操作に相当します。

[code]

    boolean isExist = false;
    for (car c : carList) {
        if (c.carId() == this.carId) {
            isExist = true;
            break;
        }
    }
    
[/code]

そうではなく、質問文の通りの、途中で`break`しないものに相当するコードにしたいのであれば、

[code]

    boolean isExist = carList.stream()
        .map(c -> c.getCarId() == this.carId)
        .reduce(false, (res, cur) -> res | cur);
    
[/code]

となります([リダクション操作](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/stream/package-
summary.html#Reduction)参照)。


CC BY-SA 4.0
2020-06-23T03:51:34.707
67911
2020-06-23T04:42:25.167
2020-06-23T04:42:25.167
2808
2808
67905
answer