Java関数式プログラミング(四):コレクション内の要素の検索

4173 ワード

要素の検索
今、私たちはこのデザインの優雅な転化集合の方法に慣れていませんが、要素を探すことにはどうしようもありません.しかし、filterの方法はこれのために生まれた.
私たちは今、名前のリストから、Nで始まる名前を取り出します.もちろん一つもないかもしれませんが、結果は空の集合かもしれません.私たちはまず古い方法で実現します.
 
  
final List startsWithN = new ArrayList();
for(String name : friends) {
if(name.startsWith("N")) {
startsWithN.add(name);
}
}

こんな简単な事件で、こんなにたくさんのコードを书いても、うるさいよ.まず変数を作成し、空の集合に初期化します.そして元の集合を巡り、指定したアルファベットで始まる名前を探します.見つかったら、集合に挿入します.
フィルタ法を用いて、上のコードを再構築し、その威力を見てみましょう.
 
  
final List startsWithN =
friends.stream()
.filter(name -> name.startsWith("N"))
.collect(Collectors.toList());

filterメソッドは、ブール値を返すlambda式を受信します.式の結果がtrueの場合、実行コンテキストの要素が結果セットに追加されます.そうでなければ、スキップします.最終的に返されるのはSteamで、式がtrueを返す要素だけが含まれています.最後に、collectメソッドを使用してこの集合をリストに変換します.後の52ページのcollectメソッドとCollectersクラスでは、このメソッドをより深く検討します.
この結果セットの要素を印刷します.
 
  
System.out.println(String.format("Found %d names", startsWithN.size()));

出力結果から明らかに,この方法は集合中に一致する要素をすべて探し出した.
 
  
Found 2 names

filterメソッドはmapメソッドと同様に反復器も返されますが、それらもこれだけです.mapが返す集合は入力集合と同じ大きさであり,filterが返すとは言い難い.返されるセットのサイズ区間は、0から入力セットの要素数までです.mapとは異なり、filterは入力セットのサブセットを返します.
これまでlambda式によるコードの簡潔性は満足していたが,注意しないとコード冗長性の問題が徐々に広がり始めた.この問題について議論しましょう.
Lambda式の再利用
Lambda式は簡潔に見えますが、実際にはうっかりするとコード冗長になりやすいです.冗長性はコードの品質が低下し、メンテナンスが困難になる.もし私たちが変更をしたいなら、いくつかの関連コードを一緒に変更しなければなりません.
冗長性を回避することで、パフォーマンスの向上にも役立ちます.関連するコードはすべて1つの場所に集中して、このように私たちはその性能の表現を分析して、それからこの場所のコードを最適化して、簡単にコードの性能を高めることができます.
次に、lambda式を使用するとコードが冗長になりやすい理由を見て、それを回避する方法を考えてみましょう.
 
  
final List friends =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
final List editors =
Arrays.asList("Brian", "Jackie", "John", "Mike");
final List comrades =
Arrays.asList("Kate", "Ken", "Nick", "Paula", "Zach");
We want to filter out names that start with a certain letter.

あるアルファベットの冒頭の名前をフィルタしたいです.まずfilter法で簡単に実現します.
 
  
final long countFriendsStartN =
friends.stream()
.filter(name -> name.startsWith("N")).count();
final long countEditorsStartN =
editors.stream()
.filter(name -> name.startsWith("N")).count();
final long countComradesStartN =
comrades.stream()
.filter(name -> name.startsWith("N")).count();

Lambda式はコードを簡潔に見せますが、いつの間にかコードの冗長性をもたらします.上記の例では、lambda式を変更するには、1つ以上の場所を変更しなければなりません.これはだめです.幸いなことに、lambda式を変数に割り当て、オブジェクトを使用するように再利用することができます.
filterメソッド、lambda式の受信者、java.util.function.Predicate関数インタフェースの参照を受信します.ここで、Javaコンパイラはまた役に立ち、指定されたlambda式でPredicateのtestメソッドの実装を生成します.パラメータ定義の場所で再生成するのではなく、Javaコンパイラにこの方法を生成させることをより明確にすることができます.上記の例では、lambda式をPredicateタイプの参照に明確に格納し、この参照をfilterメソッドに渡すことができます.これにより、コード冗長性が回避されやすくなります.
前のコードを再構築し、DRYの原則に合致させましょう.(Don't Repeat Yoursef――DRY――原則は、The Pragmatic Programmer:From Journeyman to Master[HT 00],一書を参照).
 
  
final Predicate startsWithN = name -> name.startsWith("N");
final long countFriendsStartN =
friends.stream()
.filter(startsWithN)
.count();
final long countEditorsStartN =
editors.stream()
.filter(startsWithN)
.count();
final long countComradesStartN =
comrades.stream()
.filter(startsWithN)
.count();

そのlambda式を繰り返す必要はありません.私たちは一度だけ書いて、startsWithNというPredicateタイプの参照に保存しました.この後の3つのfilter呼び出しで、JavaコンパイラはPredicateの偽装下のlambda式を見て、笑って黙って受け取った.
この新しく導入された変数は、コード冗長性を排除します.しかし不幸なことに、後ろには敵がすぐに帰ってきて恨みを晴らしているのが見えます.もっと強い武器が私たちのためにそれらを滅ぼすことができるかどうか見てみましょう.