第 2 章: コレクションの使用
私たちはさまざまなコレクション、数値、文字列、オブジェクトをよく使用します。これらはどこにでもあり、コレクションを操作するコードをわずかに最適化できたとしても、コードはより明確になります。この章では、ラムダ式を使用してコレクションを操作する方法を説明します。これを使用して、コレクションを走査し、コレクションを新しいコレクションに変換し、コレクションから要素を削除し、コレクションを結合します。
リストをたどる
リストの走査は最も基本的な集合演算であり、その演算は長年にわたっていくつかの変更を受けてきました。名前を横断する小さな例を使用して、最も古いバージョンから今日の最もエレガントなバージョンまで紹介します。
次のコードを使用すると、名前の不変のリストを簡単に作成できます。
次のようにコードをコピーします。
最終リスト<String> の友達 =
Arrays.asList("ブライアン", "ネイト", "ニール", "ラジュ", "サラ", "スコット");
System.out.println(friends.get(i));
}
以下は、リストを走査して出力する最も一般的な方法ですが、最も一般的な方法でもあります。
次のようにコードをコピーします。
for(int i = 0; i < friends.size(); i++) {
System.out.println(friends.get(i));
}
私はこの書き方を自虐的と呼んでいます。冗長で間違いが発生しやすいものです。 「i< ですか、i<= ですか?」と立ち止まって考える必要があります。これは特定の要素を操作する必要がある場合にのみ意味を持ちますが、その場合でも、次の原則に従う関数式を使用できます。不変のスタイルについては後ほど説明します。
Java は、比較的高度な構造も提供します。
次のようにコードをコピーします。
コレクション/fpij/Iteration.java
for(文字列名:友達) {
System.out.println(名前);
}
内部では、この方法での反復は Iterator インターフェイスを使用して実装され、その hasNext メソッドと next メソッドが呼び出されます。 どちらのメソッドも外部イテレータであり、その方法とやりたいことを組み合わせています。反復を明示的に制御し、どこで開始し、どこで終了するかを指示します。2 番目のバージョンは、Iterator メソッドを通じてこれを内部で実行します。明示的な操作では、break ステートメントと continue ステートメントを使用して反復を制御することもできます。 2 番目のバージョンには、最初のバージョンに欠けているものがいくつかあります。コレクションの要素を変更するつもりがない場合、このアプローチは最初のアプローチよりも優れています。ただし、これらのメソッドはどちらも必須であるため、現在の Java では廃止されるべきです。 機能的なスタイルに変更する理由はいくつかあります。
1. for ループ自体はシリアルであり、並列化が困難です。
2. このようなループはポリモーフィックではありません。得られるものは要求したものです。特定の操作を実行するためにコレクションに対して (ポリモーフィズムをサポートする) メソッドを呼び出すのではなく、コレクションを for ループに直接渡します。
3. 設計の観点から見ると、この方法で書かれたコードは「教えて、尋ねない」原則に違反します。基礎となるライブラリに反復を任せるのではなく、反復を実行することを要求します。
古い命令型プログラミングから、内部反復子のより洗練された関数型プログラミングに切り替える時期が来ました。内部イテレータを使用した後は、多くの特定の操作を基盤となるメソッド ライブラリに実行を任せるので、特定のビジネス要件にさらに集中できるようになります。基礎となる関数が反復を担当します。まず内部反復子を使用して名前のリストを列挙します。
Iterable インターフェースは JDK8 で拡張されており、forEach という特別な名前があり、Comsumer 型のパラメーターを受け取ります。名前が示すように、Consumer インスタンスは、accept メソッドを通じて渡されたオブジェクトを消費します。 forEach メソッドを使用するには、使い慣れた匿名内部クラス構文を使用します。
次のようにコードをコピーします。
friends.forEach(new Consumer<String>() { public void accept(final String name) {
System.out.println(名前);
});
friends コレクションで forEach メソッドを呼び出し、それに Consumer の匿名実装を渡しました。この forEach メソッドは、コレクション内の各要素に対して渡された Consumer の accept メソッドを呼び出し、この要素を処理できるようにします。この例では、その値 (名前) を出力するだけです。 このバージョンの出力を見てみましょう。これは前の 2 つと同じです。
次のようにコードをコピーします。
ブライアン
ネイト
ニール
ラジュ
サラ
スコット
変更した点は 1 つだけです。廃止された for ループを廃止し、新しい内部イテレータを使用しました。利点は、コレクションを反復する方法を指定する必要がなく、各要素の処理方法により集中できることです。欠点は、コードがより冗長に見えることです。これにより、新しいコーディング スタイルの楽しみがほとんどなくなってしまいます。幸いなことに、これは簡単に変更できるため、ラムダ式と新しいコンパイラの力が発揮されるのはここです。さらに変更を加えて、匿名内部クラスをラムダ式に置き換えてみましょう。
次のようにコードをコピーします。
friends.forEach((最終的な文字列名) -> System.out.println(名前));
こちらの方が見た目がずっと良くなります。コードは少ないですが、まずそれが何を意味するかを見てみましょう。 forEach メソッドは、ラムダ式またはコード ブロックを受け取り、リスト内の要素を操作する高階関数です。呼び出しごとに、コレクション内の要素が name 変数にバインドされます。基礎となるライブラリは、ラムダ式呼び出しのアクティビティをホストします。式の実行を遅らせ、必要に応じて並列計算を実行することを決定できます。 このバージョンの出力も前のバージョンと同じです。
次のようにコードをコピーします。
ブライアン
ネイト
ニール
ラジュ
サラ
スコット
内部反復子のバージョンはより簡潔です。さらに、これを使用することで、各要素を走査するのではなく、各要素の処理に集中することができます。これは宣言的です。
ただし、このバージョンには欠陥があります。 forEach メソッドの実行が開始されると、他の 2 つのバージョンとは異なり、この反復から抜け出すことはできません。 (もちろん、これを行う他の方法もあります)。したがって、この書き方は、コレクション内の各要素を処理する必要がある場合によく使用されます。後で、ループ プロセスを制御できる他の関数をいくつか紹介します。
ラムダ式の標準構文では、パラメーターを () 内に置き、型情報を提供し、カンマを使用してパラメーターを区切ります。私たちを解放するために、Java コンパイラーは自動的に型推論を実行することもできます。もちろん、型を書かない方が便利です。作業が減り、世界が静かになります。以下は、パラメータ タイプを削除した後の以前のバージョンです。
次のようにコードをコピーします。
friends.forEach((名前) -> System.out.println(名前));
この例では、Java コンパイラーはコンテキスト分析を通じて、名前のタイプが String であることを認識します。呼び出されたメソッド forEach のシグネチャを確認し、パラメータ内の関数インターフェイスを分析します。次に、このインターフェイスの抽象メソッドを分析し、パラメータの数と型をチェックします。このラムダ式が複数のパラメータを受け取った場合でも、型推論を実行できますが、この場合、すべてのパラメータがパラメータ型を持つことはできません。ラムダ式では、パラメータの型を記述するか、パラメータの型を記述する必要があります。完全に。
Java コンパイラは、単一パラメータを持つラムダ式を特別に扱います。型推論を実行する場合は、パラメータを囲む括弧を省略できます。
次のようにコードをコピーします。
friends.forEach(名前 -> System.out.println(名前));
ここには小さな注意点があります。型推論に使用されるパラメーターは最終型ではありません。型を明示的に宣言する前の例では、パラメーターを Final としてマークしました。これにより、ラムダ式のパラメーターの値を変更できなくなります。一般に、パラメータの値を変更するのは悪い習慣であり、バグが発生しやすいため、パラメータを最終としてマークすることをお勧めします。残念ながら、型推論を使用したい場合は、コンパイラが保護しなくなったため、パラメータを変更せずに自分でルールに従う必要があります。
ここまで到達するまでには多大な努力が必要でしたが、今ではコードの量は確かに少し減りました。しかし、これはまだ最も単純ではありません。この最後のミニマリスト バージョンを試してみましょう。
次のようにコードをコピーします。
friends.forEach(System.out::println);
上記のコードではメソッド参照を使用しています。コード全体をメソッド名に直接置き換えることができます。これについては次のセクションで詳しく説明しますが、ここではアントワーヌ・ド・サン=テグジュペリの有名な言葉を思い出してみましょう。「完璧とは、追加できるものではなく、もはや取り除くことができないものです。」
ラムダ式を使用すると、コレクションを簡潔かつ明確に調べることができます。次のセクションでは、削除操作やコレクションの変換を実行するときに、このような簡潔なコードをどのように記述できるかについて説明します。