翻訳アノテーション: マップ (マッピング) とリデュース (縮小、単純化) は、数学における 2 つの非常に基本的な概念であり、長い間、さまざまな関数型プログラミング言語で使用されてきましたが、Google がそれらを推進したのは 2003 年のことでした。分散システムに並列コンピューティングが実装されてから、この組み合わせの名前がコンピュータの世界で輝き始めました (機能ファンはそうは思わないかもしれません)。この記事では、Java 8 が関数型プログラミングをサポートした後の、map とreduce の組み合わせのデビューについて説明します (これは単なる予備的な紹介であり、後でそれらについて特別なトピックがあります)。
セットを減らす
これまで、コレクションを操作するためのいくつかの新しいテクニック (一致する要素の検索、個々の要素の検索、コレクションの変換) を紹介してきました。これらの操作には共通点が 1 つあり、すべてコレクション内の 1 つの要素に対して操作されるということです。要素を比較したり、2 つの要素に対して演算を実行したりする必要はありません。このセクションでは、要素を比較し、コレクションの走査中に操作結果を動的に維持する方法について説明します。
簡単な例から始めて、徐々に進めていきましょう。最初の例では、最初に friends コレクションを反復処理し、すべての名前の合計文字数を計算します。
次のようにコードをコピーします。
System.out.println("すべての名前の文字数の合計: " + friends.stream()
.mapToInt(名前 -> 名前.length())
。和());
合計文字数を計算するには、各名前の長さを知る必要があります。これは、mapToInt() メソッドを使用して簡単に実現できます。名前を対応する長さに変換したら、最後にそれらを加算するだけです。これを実現するための組み込み sum() メソッドがあります。最終的な出力は次のとおりです。
次のようにコードをコピーします。
すべての名前の文字数の合計: 26
マップ操作の変形である、mapToInt() メソッド (mapToInt、mapToDouble など。IntStream、DoubleStream などの特定の種類のストリームを生成する) を使用し、次の値に基づいて文字の総数を計算しました。返された長さ。
sum メソッドの使用に加えて、最大長を検索する max()、最小長を検索する min()、長さを並べ替えるsorted()、平均長を検索する Average() など、使用できる同様のメソッドが多数あります。平均の長さを見つけるなど、待ちます。
上記の例のもう 1 つの魅力的な側面は、ますます人気が高まっている MapReduce モードです。map() メソッドはマッピングを実行し、sum() メソッドは一般的に使用される Reduce 操作です。実際、JDK の sum() メソッドの実装では、reduce() メソッドが使用されます。より一般的に使用される形式の Reduce 操作をいくつか見てみましょう。
たとえば、すべての名前を反復処理し、最も長い名前を持つ名前を出力します。最も長い名前が複数ある場合は、最初に見つかった名前が出力されます。 1 つの方法は、最大長を計算し、この長さに一致する最初の要素を選択することです。ただし、これを行うにはリストを 2 回走査する必要があり、あまりにも非効率的です。ここで、reduce 操作が登場します。
reduce 操作を使用して 2 つの要素の長さを比較し、最も長い要素を返し、それを残りの要素とさらに比較できます。前に見た他の高階関数と同様に、reduce() メソッドもコレクション全体を走査します。特に、ラムダ式によって返された計算結果が記録されます。これをよりよく理解するのに役立つ例がある場合は、まずコードの一部を見てみましょう。
次のようにコードをコピーします。
Final Optional<String> aLongName = friends.stream()
.reduce((名前1, 名前2) ->
name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(名前 ->
System.out.println(String.format("最長の名前: %s", name)));
reduce() メソッドに渡されるラムダ式は、name1 と name2 という 2 つのパラメータを受け取り、それらの長さを比較して、最も長いパラメータを返します。 reduce() メソッドには、何をしようとしているのかわかりません。このロジックは、渡すラムダ式に取り除かれます。これは、Strategy パターンの軽量実装です。
このラムダ式は、JDK の BinaryOperator の関数インターフェイスの apply メソッドに適用できます。これはまさに、reduce メソッドが受け入れる引数のタイプです。このreduceメソッドを実行して、2つの最も長い名前のうちの最初の名前を正しく選択できるかどうかを確認してみましょう。
次のようにコードをコピーします。
一番長い名前:ブライアン
reduce() メソッドがコレクションを走査するとき、最初にコレクションの最初の 2 つの要素に対してラムダ式を呼び出し、呼び出しによって返された結果は次の呼び出しで引き続き使用されます。 2 番目の呼び出しでは、name1 の値が前の呼び出しの結果にバインドされ、name2 の値がコレクションの 3 番目の要素になります。残りの要素もこの順序で呼び出されます。最後のラムダ式呼び出しの結果は、reduce() メソッド全体によって返される結果です。
reduce() メソッドは、渡されたコレクションが空である可能性があるため、Optional 値を返します。その場合、最長の名前は存在しません。リストに要素が 1 つしかない場合、reduce メソッドはその要素を直接返し、ラムダ式を呼び出しません。
この例から、reduce の結果はセット内の最大 1 つの要素のみであることが推測できます。デフォルト値または基本値を返したい場合は、追加のパラメータを受け入れる Reduce() メソッドのバリアントを使用できます。たとえば、最も短い名前が Steve の場合、次のようにしてreduce() メソッドに渡すことができます。
次のようにコードをコピーします。
最終文字列 steveOrLonger = friends.stream()
.reduce("スティーブ", (名前1, 名前2) ->
name1.length() >= name2.length() ? name1 : name2);
それより長い名前がある場合は、この名前が選択され、そうでない場合は、基本値 Steve が返されます。このバージョンのreduce()メソッドは、Optionalオブジェクトを返しません。コレクションが空の場合は、戻り値がない場合でもデフォルト値が返されるためです。
この章を終える前に、集合演算における非常に基本的だがそれほど簡単ではない操作、つまり要素の結合を見てみましょう。
要素を結合する
要素を検索し、コレクションを走査し、変換する方法を学びました。ただし、コレクション要素の結合という別の一般的な操作があります。この新しく追加された join() 関数がなければ、前述の簡潔でエレガントなコードは無駄になってしまいます。この単純な方法は非常に実用的であるため、JDK で最もよく使用される関数の 1 つになっています。これを使用してリスト内の要素をカンマで区切って出力する方法を見てみましょう。
私たちは今でもこの友達リストを使用しています。 JDK ライブラリの古いメソッドを使用する場合、すべての名前をカンマで区切って出力したい場合はどうすればよいでしょうか?
リストを繰り返し処理し、要素を 1 つずつ出力する必要があります。 Java 5 の for ループは以前のものより改良されているので、それを使用してみましょう。
次のようにコードをコピーします。
for(文字列名:友達) {
System.out.print(名前 + ", ");
}
System.out.println();
コードは非常に単純です。その出力がどのようなものかを見てみましょう。
次のようにコードをコピーします。
ブライアン、ネイト、ニール、ラジュ、サラ、スコット、
くそー、最後にうっとうしいカンマがある(最後のスコットのせいにしてもいいだろうか?)。ここにコンマを入れないように Java に指示するにはどうすればよいでしょうか?残念ながら、ループは段階的に実行されるため、最後に特別なことを行うのは簡単ではありません。この問題を解決するには、独自のループ方法を使用できます。
次のようにコードをコピーします。
for(int i = 0; i < friends.size() - 1; i++) {
System.out.print(friends.get(i) + ", ");
}
if(友達のサイズ() > 0)
System.out.println(friends.get(friends.size() - 1));
このバージョンの出力が問題ないかどうかを確認してみましょう。
次のようにコードをコピーします。
ブライアン、ネイト、ニール、ラジュ、サラ、スコット
結果はまだ良好ですが、このコードはお世辞にも優れたものではありません。助けてください、ジャワ。
私たちはもうこの痛みに耐える必要はありません。 Java 8 の StringJoiner クラスは、これらの問題を解決するのに役立ちます。それだけでなく、String クラスは、上記のものを 1 行のコードで置き換えることができる結合メソッドも追加します。
次のようにコードをコピーします。
System.out.println(String.join(", ", friends));
見てみましょう。結果はコードと同じくらい満足のいくものです。
次のようにコードをコピーします。
ブライアン、ネイト、ニール、ラジュ、サラ、スコット
結果はまだ良好ですが、このコードはお世辞にも優れたものではありません。助けてください、ジャワ。
私たちはもうこの痛みに耐える必要はありません。 Java 8 の StringJoiner クラスは、これらの問題の解決に役立ちます。それだけでなく、String クラスには join メソッドも追加されているため、上記のものを 1 行のコードで置き換えることができます。
次のようにコードをコピーします。
System.out.println(String.join(", ", friends));
見てみましょう。結果はコードと同じくらい満足のいくものです。
次のようにコードをコピーします。
ブライアン、ネイト、ニール、ラジュ、サラ、スコット
基礎となる実装では、String.join() メソッドは StringJoiner クラスを呼び出し、最初のパラメーターを区切り文字として使用して、2 番目のパラメーター (可変長パラメーター) として渡された値を長い文字列に結合します。もちろん、この方法は単にカンマをつなぎ合わせるだけではありません。たとえば、これらの新しく追加されたメソッドとクラスのおかげで、大量のパスを渡したり、クラスパスを簡単に記述したりできます。
リスト要素を接続する方法はすでに知っています。もちろん、リストを接続する前に、map メソッドを使用してリストを変換する方法も知っています。次に、filter() メソッドを使用して、必要な要素をフィルターで除外することもできます。カンマまたはその他の区切り文字を使用してリスト要素を接続する最後のステップは、単なる単純なreduce操作です。
reduce() メソッドを使用して要素を文字列に連結できますが、これには側でいくつかの作業が必要です。 JDK には非常に便利なcollect() メソッドがあり、これはreduce() のバリエーションでもあり、要素を目的の値に結合するために使用できます。
collect() メソッドはリダクション操作を実行しますが、特定の操作は実行のためにコレクターに委任されます。変換された要素を ArrayList にマージできます。前の例を続けると、変換された要素をカンマ区切りの文字列に連結できます。
次のようにコードをコピーします。
System.out.println(
friends.stream()
.map(String::toUpperCase)
.collect(joining(", ")));
変換されたリストに対してcollect() メソッドを呼び出し、joining() メソッドによって返されたコレクターを渡しました。 Joining は、Collectors ツール クラスの静的メソッドです。コレクターはレシーバーのようなもので、collect によって渡されたオブジェクトを受け取り、それらを ArrayList、String などの必要な形式で保存します。このメソッドについては、52 ページのcollect メソッドと Collectors クラスで詳しく説明します。
これは出力名です。現在は大文字でカンマで区切られています。
次のようにコードをコピーします。
ブライアン、ネイト、ニール、ラジュ、サラ、スコット
要約する
ラムダ式を使用すると、Java のコレクション操作がよりシンプルかつ簡単になります。コレクション操作のための不格好な古いコードはすべて、このエレガントで簡潔な新しいアプローチに置き換えることができます。内部反復子により、コレクションの走査と変換がより便利になり、可変性の問題がなくなり、コレクション要素の検索が非常に簡単になります。これらの新しいメソッドを使用すると、作成するコードの量を大幅に減らすことができます。これにより、コードの保守が容易になり、ビジネス ロジックに重点が置かれ、プログラミングの基本的な操作が少なくなります。
次の章では、ラムダ式がプログラム開発におけるもう 1 つの基本操作である文字列操作とオブジェクト比較をどのように簡略化するかを見ていきます。