Java関数式プログラミング(二):集合の使用

4803 ワード

第二章:集合の使用
私たちはよくいろいろな集合、数字、文字列、オブジェクトを使います.それらはどこにでもあり、操作セットのコードを少し最適化しても、コードをはっきりさせることができます.この章では,lambda式を用いて集合を操作する方法を探索する.私たちはそれを使って集合を巡り、集合を新しい集合に変換し、集合から要素を削除し、集合をマージします.
パスリスト
遍歴リストは最も基本的な集合操作であり、ここ数年来、その操作もいくつかの変化が発生している.名前を巡る小さな例を使って、最古のバージョンから現在最も優雅なバージョンまで紹介します.
次のコードを使用すると、可変の名前のリストを簡単に作成できます.
 
  
final List friends =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
System.out.println(friends.get(i));
}

以下は、最も一般的なリストを巡回して印刷する方法です.
 
  
for(int i = 0; i < friends.size(); i++) {
System.out.println(friends.get(i));
}

私はこの方法を自虐的な書き方と呼んでいます.うるさいし、間違いやすいです.立ち止まってよく考えなきゃ
Javaはまた、比較的先進的なfor構造を提供しています.
 
  
collections/fpij/Iteration.java
for(String name : friends) {
System.out.println(name);
}

下位レベルでは、この方法の反復は、Iteratorインタフェースを使用して実現され、hasNextメソッドとnextメソッドが呼び出されます.この2つの方法は、外部反復器に属し、どのようにするかと何をしたいかを練り合わせています.反復を明示的に制御し、どこからどこまで終了するかを示します.2番目のバージョンは、下位レベルでIteratorメソッドによってこれらを行います.明示的な操作では、break文とcontinue文で反復を制御することもできます.2番目のバージョンは1番目より少し少ないです.集合の要素を変更するつもりがなければ、1番目よりも良い方法です.しかし、この2つの方法はコマンド式で、現在のJavaではこの方法を捨てるべきです.関数式に変更する理由はいくつかあります.
1.forループ自体はシリアルであり、並列化が困難である.2.このようなループは非マルチステートであり、求められるものである.集合にメソッド(マルチステートをサポートする)を呼び出して特定の操作を実行するのではなく、直接forループに集合を渡す.3.設計面から言えば、このように書かれたコードは「Tell,Don't Ask」に違反しているの原則です.反復を最下位ライブラリに残して実行するのではなく、反復を実行するように要求します.
古いコマンド・プログラミングからより優雅な内部反復器の関数プログラミングに移行する時です.内部反復器を使用すると、多くの具体的な操作を下位メソッド・ライブラリに捨てて実行します.具体的なビジネスニーズに集中できます.下位の関数は反復を担当します.まず、内部反復器を使用して名前のリストを列挙します.
IterableインタフェースはJDK 8で強化されており、forEachという名前が付いており、Consumerタイプのパラメータを受信しています.名前のように、Consumerのインスタンスはacceptメソッドによってオブジェクトに渡されます.このforEachメソッドを使用するには、よく知られている匿名の内部クラスの構文を使用します.
 
  
friends.forEach(new Consumer() { public void accept(final String name) {
System.out.println(name); }
});

私たちはfriendsコレクション上のforEachメソッドを呼び出し、Consumerの匿名実装を渡しました.このforEachメソッドは、コレクション内の各エレメントに対して入力されたConsumerのacceptメソッドを呼び出し、このエレメントを処理させます.この例では、その値、すなわちこの名前を印刷しただけです.このバージョンの出力結果を見てみましょう.前の2つの結果と同じです.
 
  
Brian
Nate
Neal
Raju
Sara
Scott

私たちはただ1つの場所を変更しました:私たちは古いforサイクルを捨てて、新しい内部反復器を使用しました.メリットは、この集合をどのように反復するかを指定する必要がなく、各要素をどのように処理するかに集中することができます.欠点は、コードがもっとうるさいように見えます――これはまるで新しいコードスタイルがもたらした喜びをきれいにします.幸いなことに、これはとても簡単に変更できます.これはlambda式と新しいコンパイラの威力が大いに発揮される時です.匿名の内部クラスをlambda式に変更します.
 
  
friends.forEach((final String name) -> System.out.println(name));

これでだいぶよくなったように見えます.コードはもっと少ないですが、まずこれがどういう意味かを見てみましょう.このforEachメソッドは、リスト内の要素を操作するためにlambda式またはコードブロックを受信する高次関数です.呼び出しのたびに、アセンブリ内の要素はnameという変数にバインドされます.下位ライブラリはlambda式呼び出しのアクティビティを管理しています..遅延式の実行を決定し、適切であれば並列計算も可能です.このバージョンの出力も前と同じです.
 
  
Brian
Nate
Neal
Raju
Sara
Scott

内部反復器のバージョンはより簡潔です.そして、それを使用すると、どのように遍歴するかではなく、各要素の処理操作に集中することができます.これは宣言的です.
しかし、このバージョンには欠陥があります.forEachメソッドが実行されると、他の2つのバージョンとは異なり、この反復から飛び出すことはできません.(もちろん、これを解決する別の方法があります).そのため、この書き方は、集合内の各要素を処理する必要がある場合によく使われます.後で、ループを制御する別の関数について説明します.
Lambda式の標準文法は、パラメータを()に入れ、タイプ情報を提供してカンマでパラメータを区切ることです.Javaコンパイラは私たちを解放するために、自動的にタイプを導くこともできます.タイプを書かないほうが便利です.仕事が少なく、世界も静かになりました.次のバージョンは、パラメータタイプを削除した後です.
 
  
friends.forEach((name) -> System.out.println(name));

この例では、Javaコンパイラはコンテキスト解析によって、nameのタイプがStringであることを知っています.呼び出されたメソッドforEachの署名を表示し、パラメータのこの関数式インタフェースを解析します.次に、このインタフェースの抽象的なメソッドを解析し、パラメータの個数とタイプを表示します.このlambda式が複数のパラメータを受信しても、私たちは同じようにタイプ導出を行うことができます.ただし、これではすべてのパラメータにパラメータタイプを付けることはできません.lambda式では、パラメータタイプはすべて書かないか、書くにはすべて書かなければなりません.
Javaコンパイラは、単一のパラメータのlambda式を特殊に処理します.タイプの導出を行う場合は、パラメータの両側のカッコを省略できます.
 
  
friends.forEach(name -> System.out.println(name));

ここでは、タイプ導出を行うパラメータはfinalタイプではないという小さな警告があります.前に明示的にタイプを宣言した例では、パラメータをfinalと表記しています.これによりlambda式でパラメータの値を変更することを防止できます.通常、パラメータの値を変更するのは悪い習慣であり、BUGを引き起こしやすいのでfinalと表記するのは良い習慣です.残念なことに、タイプガイドを使用したい場合は、コンパイラが私たちのために護衛してくれないので、ルールを守ってパラメータを変更しないでください.
ここまで来て苦労しましたが、コードの量は確かに少なくなりました.しかし、これはまだ最も簡単ではありません.最後のこの極簡版を体験してみましょう.
 
  
friends.forEach(System.out::println);

上記のコードでは、メソッドリファレンスを使用しています.メソッド名でコード全体を直接置き換えることができます.次のセクションでは、これを深く検討しますが、Antoine de Saint-Exupéryの名言を思い出してみましょう.完璧はこれ以上追加できないものではなく、何も削除できないものです.
Lambda式は、集合の遍歴を簡潔かつ明瞭に行うことができます.次の節では、削除操作や集合変換を行うときに、このような簡潔なコードを書く方法について説明します.