データ例
次のようなデータを用意します.
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<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は次のようなクラスです.
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文での記法
for (Staff staff : StaffList) {
    for (Roll roll : RollList) {
        if (roll.isOwner()) {
            if (staff.getRollType() == roll.getRollType()) {
                System.out.println(staff.getName());
            }
        }
    }
}
わかりやすいですね.
Streamでの記法
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()));
簡単に解説すると,
- 内部条件がtrueとなるStaffをfilterする
- ownerがtrueであるRollリストを生成
- 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で二つの配列から重複する要素を削除したり、独自クラスを色々する方法