lambdaについて調べてみた filter編 その1

【前提条件】

[環境]

【概要】

今回はfilterについて調べてみました。

filterはStream内の要素に対して条件でフィルタするメソッドです。
フィルタでtrueが帰ってきた要素のみ後続の処理が行われます。

【単一のfilter】

まずはListから単一の条件でフィルタして、
コンソールに出力するサンプルからです。

[サンプルソース]
    public static void main(final String[] args) {
        
        final List<String> list = new ArrayList<>();

        list.add("one");
        list.add("two");
        list.add("three");
        list.add("four");

        System.out.println("======================== outputStringList ========================");
        list.stream().filter(p -> 3 < p.length()).forEach(System.out::println);
        System.out.println("======================== outputStringList ========================");
    }

filterメソッドはPredicateのオブジェクトをパラメータとして渡します。
Predicateインターフェイスはbooleanを返すtestメソッドが定義されています。
ラムダ式ではbooleanの結果を返すものを書くだけです。

[実行結果]
three
four

文字列の長さが3を超える値のみが取得されているのがわかります。

【複数のFilter】

次にBeanの中身を複数の条件でフィル足して、
コンソールに出力してみます。

[Bean]
public class Personal {

    public final String name;
    public final int age;
    public final Arm arm;

    public Personal(final String name, final int age, final Arm arm) {

        this.name = name;
        this.age = age;
        this.arm = arm;
    }

    public boolean isSearchMathing() {

        return (40 < age && Arm.Right.equals(arm));
    }

    public enum Arm {
        Right,
        Left
    }
}

isSearchMathingメソッドはあとで
メソッド参照させるときに使うためのものです。

[サンプルソース]
    public static void main(final String[] args) {

        final List<Personal> list = new ArrayList<>();

        list.add(new Personal("シュンツ", 27, Personal.Arm.Right));
        list.add(new Personal("コーツ", 38, Personal.Arm.Left));
        list.add(new Personal("カンツ", 61, Personal.Arm.Right));

        list.add(new Personal("ピンフ", 44, Personal.Arm.Left));
        list.add(new Personal("トイトイ", 52, Personal.Arm.Left));
        list.add(new Personal("チートイツ", 49, Personal.Arm.Right));

        list.stream()
                .filter(p -> 40 < p.age)
                .filter(p -> Personal.Arm.Right.equals(p.arm))
                .forEach(p -> System.out.println(p.name));

        list.stream()
                .filter(p -> 40 < p.age && Personal.Arm.Right.equals(p.arm))
                .forEach(p -> System.out.println(p.name));

        list.stream()
                .filter(Personal::isSearchMathing)
                .forEach(p -> System.out.println(p.name));
    }

複数の条件でフィルタをする場合にはfilterをメソッドチェインでつなげます。
もしくはfilterの中で複数の条件を書いたり、
Beanに条件用のメソッドを作ってメソッド参照で書いたりしてもできます。

[実行結果]
カンツ
チートイツ

サンプルソースの3パターンとも同じ結果が返ってきます。
ageが40を超えるもので、ArmがRightのデータのみが出力されています。

【単一と複数の違い】

複数の条件でフィルタをする場合、
単一のfilterメソッドでやるのと複数のfilteメソッドチェインで行うのと
動きがどう変わるかを見てみます。

[サンプルソース]
    public static void main(final String[] args) {

        // listの作成は先ほどのサンプルと同じなので省略

        System.out.println("======================== outputPersonalList2 ========================");

        final List<Personal> resultList = list.stream()
                .filter(p -> {
                    final boolean returnValue = 40 < p.age;
                    System.out.println(p.name + "is 40 over?" + returnValue);
                    return returnValue;
                })
                .filter(p -> {
                    final boolean returnValue = Personal.Arm.Right.equals(p.arm);
                    System.out.println(p.name + "is right arm?" + returnValue);
                    return returnValue;
                })
                .collect(Collectors.toList());

        
        System.out.println("------------------------ result ------------------------");
        resultList.forEach(p -> System.out.println(p.name));
        System.out.println("------------------------ result ------------------------");
        
        System.out.println("======================== outputPersonalList2 ========================");
    }

各filterメソッドを呼び出している際に
コンソール出力させるようにしてみました。

[実行結果]
シュンツis 40 over?false
コーツis 40 over?false
カンツis 40 over?true
カンツis right arm?true
ピンフis 40 over?true
ピンフis right arm?false
トイトイis 40 over?true
トイトイis right arm?false
チートイツis 40 over?true
チートイツis right arm?true
------------------------ result ------------------------
カンツ
チートイツ
------------------------ result ------------------------

名前が「シュンツ」、「コーツ」と言うデータは
最初のfilter条件を満たしていないため、
二つ目のfilterメソッドが実行されていないことがわかります。

filter(A).filter(B).filter(C)というのがあった場合、
データごとにA -> B -> Cの順番で評価されるようです。

つまり、最初の条件で絞ってしまったほうが
後続の処理は少なくてすむということです。

【まとめ】

filterメソッドを使えば、
条件で後続の処理対象を絞れました。

filterメソッドをメソッドチェインで呼び出す場合は
定義した順番で評価されていきます。
データ件数が多い場合はfilterの仕方でパフォーマンスが
変わってきそうですね。