Help us understand the problem. What is going on with this article?

【Java】Stream APIを使って複数のリストから条件を絞ったリストを生成する

データ例

次のようなデータを用意します.

Staffリスト

name rollType
佐藤 1
鈴木 2
高橋 3
田中 4
伊藤 5

Rollリスト

rollType owner
1 true
2 true
3 false
4 false
5 false

DBではよく見る構造ではないでしょうか.
ownerがtrueであるrollTypeをもつStaffのリストが欲しい時,どうしますか?
「DBからINNER JOINで持って来いよ」と思いますが,今回は独立にリストが存在することを仮定します.

実際に用意したリストは次のようになります.

list.java
List<Staff> staffList = Arrays.asList(
        new Staff("佐藤", 1), 
        new Staff("鈴木", 2), 
        new Staff("高橋", 3), 
        new Staff("田中", 4), 
        new Staff("伊藤", 5)
);

List<Roll> rollList = Arrays.asList(
        new Roll(1, true), 
        new Roll(2, true), 
        new Roll(3, false), 
        new Roll(4, false), 
        new Roll(5, false)
);

Staff,Rollは次のようなクラスです.

SampleClass.java
private class Staff {
    private final String name;
    private final Integer rollType;

    public Staff(String name, Integer rollType) {
        this.name = name;
        this.rollType = rollType;
    }

    public String getName() {
        return this.name;
    }

    public Integer getRollType() {
        return this.rollType;
    }
}

private class Roll {
    private final Integer rollType;
    private final boolean owner;

    public Roll(Integer rollType, boolean owner) {
        this.rollType = rollType;
        this.owner = owner;
    }

    public Integer getRollType() {
        return this.rollType;
    }

    public boolean isOwner() {
        return this.owner;
    }
}

実際に取ってくる

以下にテーマであるStream,比較対象としてfor文を用意します.

for文での記法

Main.java
for (Staff staff : StaffList) {
    for (Roll roll : RollList) {
        if (roll.isOwner()) {
            if (staff.getRollType() == roll.getRollType()) {
                System.out.println(staff.getName());
            }
        }
    }
}

わかりやすいですね.

Streamでの記法

Main.java
staffList.stream()
        .filter( // 1.
                staff ->
                        rollList.stream()
                                .filter(roll -> roll.isOwner()) //2.
                                .anyMatch(roll -> // 3.
                                        staff.getRollType() == roll.getRollType()
                                )
        )
        .forEach(staff -> System.out.println(staff.getName()));

簡単に解説すると,

  1. 内部条件がtrueとなるStaffをfilterする
  2. ownerがtrueであるRollリストを生成
  3. Rollリスト内にStaffのrollTypeがあるならtrueを返す

となります.

実行結果はどちらも

佐藤
鈴木

となります.

実行時間の比較

それぞれの記法を用いて条件を満たす新たなリストを生成(forは空のリストに対するadd,StreamはCollectorを使用),
これを1000000回繰り返した時の実行時間は次のようになりました.

記法 実行時間(ms)
For 135
Stream 889

実に6倍の差がついていますね.
何度か試しましたが,この差が大きく縮まることはありませんでした(当たり前).

もともとStreamが高速でないのに加え,filter内でいちいちStreamを生成しているのが大きく響いているのでしょう.

簡単なループならfor文よりstream派なのですが,ここまでパフォーマンスに差がでるのは考え物ですね.

まとめ

Streamを用いて疑似的なINNER JOINを達成
filterを入れ子にすることでリストのリスト・・・といったものにも対応可能
パフォーマンスの観点からみてFor文をおとなしく使おう

もしこれと同等の操作をより高速にできる手法がありましたらコメントお願いします.

参考文献

Java Stream APIをいまさら入門
JavaのStreamで二つの配列から重複する要素を削除したり、独自クラスを色々する方法

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away