私たちはラムダが Java にクロージャの概念をもたらすのを長い間待っていましたが、コレクションでラムダを使用しない場合、多くの価値を失います。既存のインターフェイスをラムダ スタイルに移行するという問題は、デフォルトのメソッドによって解決されました。この記事では、Java コレクションのバルク データ操作 (バルク オペレーション) を深く分析し、ラムダの最も強力な役割の謎を解明します。
1.JSR335について
JSR は Java Specific Requests の略で、Java 仕様リクエストを意味します。Java 8 バージョンの主な改良点は、Java でマルチコア プロセッサ向けのコードを書きやすくすることを目的とした Lambda プロジェクト (JSR 335) です。 JSR 335=ラムダ式 + インターフェイスの改善 (デフォルトのメソッド) + バッチ データ操作。前の 2 つの記事と合わせて、JSR335 の関連内容を完全に学習しました。
2. 外部反復と内部反復
以前は、Java コレクションは内部反復を表現できず、外部反復の 1 つの方法、つまり for ループまたは while ループのみを提供していました。
次のようにコードをコピーします。
人物のリスト = asList(新しい人物("ジョー"), 新しい人物("ジム"), 新しい人物("ジョン"));
for (人 p : 人) {
p.setLastName("Doe");
}
上の例は、いわゆる外部反復である以前のアプローチです。ループは固定シーケンス ループです。今日のマルチコア時代では、並列ループを実行したい場合は、上記のコードを変更する必要があります。効率がどの程度向上するかはまだ不確実であり、一定のリスク (スレッドの安全性の問題など) が生じる可能性があります。
内部反復を記述するには、Lambda のようなクラス ライブラリを使用する必要があります。lambda と Collection.forEach を使用して上記のループを書き直してみましょう。
次のようにコードをコピーします。person.forEach(p->p.setLastName("Doe"));
これで、jdk ライブラリがループを制御します。各人物オブジェクトに姓がどのように設定されるかを気にする必要はありません。実行環境に応じて、並列、アウトオブオーダー、または遅延でそれを行う方法を決定できます。ロード中。これは内部反復であり、クライアントは動作 p.setLastName をデータとして API に渡します。
実際、内部反復はコレクションのバッチ操作と密接な関係はありませんが、そのおかげで文法表現の変化を感じることができます。バッチ操作に関連して本当に興味深いのは、新しいストリーム API です。新しい java.util.stream パッケージが JDK 8 に追加されました。
3.ストリームAPI
ストリームはデータ フローのみを表し、データ構造を持たないため、一度通過した後は通過することはできません (プログラミングの際には注意が必要です。コレクションとは異なり、何度行ってもデータが存在します)トラバースされます)。ソースはコレクション、配列、io などです。
3.1 中間およびエンドポイントの方法
ストリーミングはビッグ データを操作するためのインターフェイスを提供し、データ操作をより簡単かつ高速にします。フィルタリング、マッピング、トラバーサル数の削減などのメソッドがあり、これらのメソッドは中間メソッドと終端メソッドの 2 つのタイプに分けられます。中間メソッドは本質的に連続的なものである必要があります。最終結果を取得したい場合は、エンドポイント操作を使用して、ストリームによって生成された最終結果を収集する必要があります。これら 2 つのメソッドの違いは、戻り値に注目することです。ストリームの場合は中間メソッドであり、そうでない場合は終了メソッドです。詳細については、Stream の API を参照してください。
いくつかの中間メソッド (フィルター、マップ) とエンドポイント メソッド (収集、合計) を簡単に紹介します。
3.1.1フィルター
データ ストリームにフィルタリング関数を実装することは、私たちが考える最も自然な操作です。 Stream インターフェイスは、フィルター条件を定義するラムダ式を使用する操作を表す Predicate 実装を受け入れるフィルター メソッドを公開します。
次のようにコードをコピーします。
人物のリスト = …
Stream personOver18 = person.stream().filter(p -> p.getAge() > 18);//18 歳以上の人々をフィルタリングします
3.1.2地図
オブジェクトの変換時などに、いくつかのデータをフィルタリングするとします。 Map 操作を使用すると、入力パラメータを受け入れて返す Function の実装を実行できます (Function<T, R> の汎用 T と R はそれぞれ実行入力と実行結果を表します)。まず、匿名の内部クラスとして記述する方法を見てみましょう。
次のようにコードをコピーします。
ストリーム大人 = 人
。ストリーム()
.filter(p -> p.getAge() > 18)
.map(新しい関数() {
@オーバーライド
public 大人の応募(本人) {
return new Adult(person);//18 歳以上の人を大人に変換します
}
});
次に、上記の例をラムダ式に変換します。
次のようにコードをコピーします。
ストリームマップ = people.stream()
.filter(p -> p.getAge() > 18)
.map(人 -> 新しい成人(人));
3.1.3カウント
count メソッドはストリームのエンドポイント メソッドで、ストリーム結果の最終統計を作成し、int を返すことができます。たとえば、18 歳以上の人の合計数を計算してみましょう。
次のようにコードをコピーします。
int countOfAdult=persons.stream()
.filter(p -> p.getAge() > 18)
.map(人 -> 新成人(人))
。カウント();
3.1.4収集
collect メソッドは、最終結果を収集できるストリームのエンドポイント メソッドでもあります。
次のようにコードをコピーします。
リスト AdultList= person.stream()
.filter(p -> p.getAge() > 18)
.map(人 -> 新成人(人))
.collect(Collectors.toList());
または、特定の実装クラスを使用して結果を収集したい場合は、次のようにします。
次のようにコードをコピーします。
リストの大人のリスト = 人
。ストリーム()
.filter(p -> p.getAge() > 18)
.map(人 -> 新成人(人))
.collect(Collectors.toCollection(ArrayList::new));
スペースが限られているため、他の中間メソッドとエンドポイント メソッドを 1 つずつ紹介することはしません。上記の例を読んだ後は、これら 2 つのメソッドの違いを理解するだけで済み、必要に応じて使用することができます。後で。
3.2 シーケンシャルフローとパラレルフロー
各ストリームには、順次実行と並列実行の 2 つのモードがあります。
シーケンスの流れ:
次のようにコードをコピーします。
リスト <人> 人 = list.getStream.collect(Collectors.toList());
並列ストリーム:
次のようにコードをコピーします。
リスト <人> 人 = list.getStream.Parallel().collect(Collectors.toList());
名前が示すように、順次メソッドを使用してトラバースすると、次の項目が読み取られる前に各項目が読み取られます。並列トラバーサルを使用する場合、配列は複数のセグメントに分割され、それぞれが異なるスレッドで処理され、結果がまとめて出力されます。
3.2.1 並列ストリームの原則:
次のようにコードをコピーします。
リストoriginalList = someData;
split1 =originalList(0,mid);//データを小さな部分に分割します
split2 = オリジナルリスト(中間,末尾);
new Runnable(split1.process());//操作を細かく分割して実行する
new Runnable(split2.process());
List RevededList = Split1 + Split2;//結果をマージします
3.2.2 逐次パフォーマンス テストと並列パフォーマンス テストの比較
マルチコア マシンの場合、理論的には並列ストリームはシーケンシャル ストリームの 2 倍の速度になります。以下はテスト コードです。
次のようにコードをコピーします。
長い t0 = System.nanoTime();
// 100 万の範囲の整数ストリームを初期化し、2 で割り切れる数値を見つけます。 toArray() はエンドポイント メソッドです
int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();
長い t1 = System.nanoTime();
//上記と同じ関数。ここでは並列ストリームを使用して計算します。
int b[]=IntStream.range(0, 1_000_000).Parallel().filter(p -> p % 2==0).toArray();
長い t2 = System.nanoTime();
//私のローカル マシンの結果は、シリアル: 0.06 秒、パラレル 0.02 秒で、パラレル フローがシーケンシャル フローよりも実際に高速であることを証明しています。
System.out.printf("シリアル: %.2fs、パラレル %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);
3.3 Folk/Joinフレームワークについて
アプリケーション ハードウェアの並列処理は Java 7 で利用できます。java.util.concurrent パッケージの新機能の 1 つは、非常に強力で効率的なものです。興味のある学生はぜひ勉強してください。詳細については、ここで説明します。Stream.Parallel() と比較して、私は後者のほうを好みます。
4. まとめ
ラムダがないと、Stream は非常に使いにくくなります。デフォルトのメソッドがない場合、上記の 3.1.2map の例のように、大量の匿名内部クラスが生成されます。したがって、lambda+default メソッドによって JDK ライブラリがより強力かつ柔軟になることは、ストリームとコレクション フレームワークの改善が何よりの証拠です。