4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

リストの内容を足し算するラムダを勉強した

Last updated at Posted at 2021-02-18
  • 環境 : 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を使った書き方

配列を使って、処理を並列化(parallelメソッド)するという手段があるようですが計算が狂うそうです。
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を使った書き方
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って聞くと引き算みたいでやりたいことと違うイメージ。

image.png
リダクションには、それぞれの部分計算の間の依存性を極力排除することによって効果的に並列実行できるというメリットがあります。
Java SE 8のストリームAPIの正しい使い方──ラムダ式とともに導入された新APIで、並列処理の実装はどう変わるのか? - page3 - builder by ZDNet Japan

StreamAPIのreduceメソッドを使った書き方
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);

これは終端操作です。
IntStream (Java Platform SE 8) - docs.oracle.com

StreamAPIのsumメソッドを使った書き方
List<Integer> list = Arrays.asList(2021, 2, 18);
int total = list.stream().mapToInt(val -> val).sum();
System.out.println(total);
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?