- 環境 : openjdk 11.0.8
ラムダを書けるようになりたい!苦手を克服したい!
と思って・・・こんなforeachをラムダ風にしようと思います。
List<Integer> list = Arrays.asList(2021, 2, 18);
int total = 0;
for (var val : list) {
total += val;
}
System.out.println(total);
(間違い)forEachメソッドとStreamAPIのmapメソッドを使った書き方
以下どちらの方法も
Local variable total defined in an enclosing scope must be final or effectively final
とコンパイルエラーになります。
// forEachメソッドで書いてみた
list.forEach(val -> total += val);
// StreamAPIのmapメソッドで書いてみた
list.stream().map(val -> total += val);
「外にある変数(total)はfainalじゃなきゃだめです。」とおっしゃっている。
ラムダ式の中からは外側のローカル変数を参照することができますが、値を書き換えることは禁止されています。また、参照できるローカル変数は、final宣言されているか、初期化以外で値を書き換えていない変数に限られます。
Java SE 8のストリームAPIの正しい使い方──ラムダ式とともに導入された新APIで、並列処理の実装はどう変わるのか? - page3 - builder by ZDNet Japan
(非推奨)AtomicIntegerを使った書き方
int total[] = {0};
list.stream().parallel().forEach(val -> total[0] += val);
System.out.println(total[0]);
ただし、このコードでは、実行する度に異なる計算結果が出力されてしまうでしょう。「+=」はアトミックな処理ではないため、並列化することでsum[0]の値が同時に書き換えられて結果が狂う可能性があるのです。
Java SE 8のストリームAPIの正しい使い方──ラムダ式とともに導入された新APIで、並列処理の実装はどう変わるのか? - page3 - builder by ZDNet Japan
アトミック?何それ?
原子性(アトミック性)とは、あるスレッド上での、あるデータへの複数の操作が、他のスレッドからみて単一の操作に見えること。データの状態遷移の過渡的な不整合な状態が見えない性質とも言う。
Java のプリミティブ型のうち、long と double 以外の型の操作は原子性が保証されている。long と double は 64bit のデータで、その読み書きの際に複数の操作が発生するため原子性が保証されない。
一方で、int 等の整数のインクリメント操作やデクリメント操作の記法(count++;
やcount--;
)は、その操作の中に複数の操作(読み取り、加算or減算、書き込み)を含むため、原子性が保証されない。
Javaの活用 - mixi-inc/AndroidTraining
AtomicInteger total = new AtomicInteger(0);
list.stream().parallel().forEach(val -> total.addAndGet(val));
System.out.println(total.get());
しかし、これもお勧めできない方法です。sumをAtomicLongにしたということは、ストリームにおいて1つの要素の処理中に、他の要素が待ち状態になるということにほかなりません。それでは並列化する意味がないばかりか、並列化によるオーバーヘッドで通常よりもパフォーマンスが落ちてしまうからです。
Java SE 8のストリームAPIの正しい使い方──ラムダ式とともに導入された新APIで、並列処理の実装はどう変わるのか? - page3 - builder by ZDNet Japan
StreamAPIのreduceメソッドを使った書き方
リダクション(英: reduction)とは、英語の発音における音声の脱落現象のこと
リダクション - Wikipedia
へぇぇ・・・英語としてはこんな意味なんだ。
reduce(T identity, BinaryOperator accumulator)
指定された単位元の値と結合的な累積関数を使ってこのストリームの要素に対してリダクションを実行し、リデュースされた値を返します。
Stream (Java Platform SE 8) - docs.oracle.com
reduceって聞くと引き算みたいでやりたいことと違うイメージ。
リダクションには、それぞれの部分計算の間の依存性を極力排除することによって効果的に並列実行できるというメリットがあります。
Java SE 8のストリームAPIの正しい使い方──ラムダ式とともに導入された新APIで、並列処理の実装はどう変わるのか? - page3 - builder by ZDNet Japan
List<Integer> list = Arrays.asList(2021, 2, 18);
Optional<Integer> total = list.stream().parallel().reduce((v1, v2) -> v1 + v2);
System.out.println(total.get());
ただ足し算するだけなのにコードがなんか・・・足し算っぽくない・・・レベルが低いからそう感じるのか・・・
StreamAPIのsumメソッドを使った書き方
「sum」は、ストリーム上の各要素の「数値データ」の総計を求めるメソッドです。
(省略)
メソッド「sum」を使うには、(省略)、取り出した値だけをストリームにする必要があります。
つまり、見えないところで数値だけの要素からなるリストを作るのです。
書籍情報―Java8ではじめる「ラムダ式」
StreamAPIのmapToIntを使ってintのストリームを作るようです。
IntStream mapToInt(ToIntFunction super T> mapper)
このストリームの要素に指定された関数を適用した結果から構成されるIntStreamを返します。
これは中間操作です。
Stream (Java Platform SE 8) - docs.oracle.com
reduceメソッドと同じことらしいけどsumってお名前が合計っぽく見えていい感じ。
int sum()
このストリーム内の要素の合計を返します。これはリダクションの特殊な場合であり、次と同等になります。return reduce(0, Integer::sum);
List<Integer> list = Arrays.asList(2021, 2, 18);
int total = list.stream().mapToInt(val -> val).sum();
System.out.println(total);