🎏

(Java)複数のオブジェクトのリストの差分をstream APIで取得する

2021/11/30に公開

OracleのJava資格の勉強中、複数のオブジェクトのリストの差分だけ入っているリストを作る時どうにかforを使わずにstream APIで出来ないか、と突如思ったので考えてみました。

今回のケース

name, accountId, countryフィールドをもつAccountオブジェクトのリストを2つ用意し、それぞれ更新前のアカウント情報と更新後のアカウント情報を表すリストとする。
追加されたアカウント、もしくは情報が更新されたアカウントだけを抽出し、Accountクラスのリストで返す。

実装

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

      List<Account> beforeAccountList = new ArrayList<>();
    List<Account> afterAccountList = new ArrayList<>();
    
        // データ更新前のリスト
    beforeAccountList.add(new Account("Kate", "USA", 9782345));
    beforeAccountList.add(new Account("Dylan", "Canada", 2039735));
    beforeAccountList.add(new Account("Maruta", "Japan", 45786590));

        // データ更新後のリスト
    afterAccountList.add(new Account("Kate", "EU", 9782345));
    afterAccountList.add(new Account("Dylan", "Canada", 2039735));
    afterAccountList.add(new Account("Maruta", "Japan", 45786590));
    afterAccountList.add(new Account("Tanaka", "Japan", 903279457));

    // 国が変わっているKate, 新しく追加されたTanakaのアカウントを抽出する
    List<Account> accountDiffList =
      afterAccountList.stream()
        .filter(account ->
          beforeAccountList.stream()
            .noneMatch(before ->
              before.getName().equals(account.getName()) &&
                before.getCountry().equals(account.getCountry()) &&
                before.getAccountId() == account.getAccountId())
          )
        .collect(Collectors.toList());

        // メッセージ出力
    accountDiffList.stream()
      .forEach(account -> System.out.println(account.getName() + "(" + account.getCountry() + "): " + account.getAccountId()));
  }

出力結果

Kate(EU): 9782345
Tanaka(Japan): 903279457

同じ処理をforで書くと以下のようになります。

for(Account after : afterAccountList) {
      boolean hasSameAccount = false;
      for (Account before : beforeAccountList) {
        if (before.getName().equals(after.getName()) &&
          before.getCountry().equals(after.getCountry()) &&
          before.getAccountId() == after.getAccountId()) {
          hasSameAccount = true;
          break;
        }
      }
      if(!hasSameAccount) accountDiffList.add(after);
}

所感

個人的にはstreamの方が書きやすい印象を持ちました。しかしstreamも入れ子になる(filterの中にstreamを作っている)ので、読みやすさの点では劇的に変わっていないかも、とも思いました。
また今回のテストデータは数が少ないものの、大量のデータを捌かないといけなくなった時2重forループと2重streamとの間でどれだけパフォーマンスに差が出るのか未検証です...。ので真似する時はご注意下さい。もっと良い書き方あるよ〜という方いればコメント大歓迎です

Discussion