1. 導入:なぜStream.distinct()が重要なのか
実務におけるデータ処理では、外部APIからのレスポンスやDBからの検索結果など、重複するデータを含むリストを扱う機会が多々あります。手動でforループを回して重複チェックを行うコードは冗長でミスを誘発しやすく、また可読性も下がります。Java 8以降で導入されたStream.distinct()を利用すれば、宣言的かつ簡潔にこの課題を解決でき、コードの保守性を劇的に向上させることが可能です。
2. 基礎知識:distinct()の仕組み
Stream.distinct()は、Stream APIが提供する中間操作の一つです。このメソッドは、Objectクラスのequals()とhashCode()を内部的に使用して重複を判定します。つまり、対象となるオブジェクトが適切にこれらのメソッドをオーバーライドしていることが、正しく動作するための前提条件となります。Setコレクションと同様の仕組みで重複を排除するため、非常に効率的です。
3. 実装と解決策
基本的には、リストをストリームに変換し、distinct()を呼び出し、collect()で再度リストに戻すというフローが一般的です。もし特定のフィールド(例:ID)のみを基準に重複を除去したい場合は、カスタムComparatorを用いるか、あるいは一度Mapに変換してキーで重複を排除する「Mapのキーによる抽出」といった手法を組み合わせるのが実務上の定石です。
4. サンプルプログラム
以下は、ユーザー名を持つUserクラスのリストから、名前が重複している要素を取り除く実用的なコード例です。
import java.util.;
import java.util.stream.Collectors;
public class DistinctExample {
public static void main(String[] args) {
// テスト用データ:重複を含むリスト
List
new User(1, “佐藤”),
new User(2, “鈴木”),
new User(3, “佐藤”) // 名前が重複
);
// stream.distinct()を利用した重複除去
// 注意:UserクラスでequalsとhashCodeを実装している必要がある
List
.distinct()
.collect(Collectors.toList());
// 結果の確認
distinctUsers.forEach(u -> System.out.println(u.getName()));
}
}
// 比較対象となるクラス
class User {
private int id;
private String name;
public User(int id, String name) { this.id = id; this.name = name; }
public String getName() { return name; }
// 重複判定のためにhashCodeとequalsのオーバーライドが必須
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
5. 応用・注意点:現場で陥りやすい罠
実務で最も注意すべき点は、equals()とhashCode()の実装漏れです。これらを実装していない場合、Objectクラスのデフォルトの挙動(メモリ上のアドレス比較)が行われ、見た目が同じオブジェクトでも重複が除去されません。
また、Sequenced Collections(Java 21以降)を利用する場合、Listの順序を保持したまま一意な要素を取得したいケースが多いですが、distinct()は順序を維持する性質(stable)があるため、安心して利用可能です。もし、巨大なデータセットを扱う場合は、メモリ消費量に注意してください。distinct()内部では、過去に出現した要素を保持するためにSetが内部的に生成されるため、メモリを大量に消費する可能性があります。大量データを扱う際は、可能な限り事前にフィルタリングを行うか、並列処理(parallelStream)のオーバーヘッドを考慮して設計することをお勧めします。

コメント