LoginSignup
2
2

More than 3 years have passed since last update.

【Java】独自クラスのListで重複した要素を除外する

Last updated at Posted at 2021-03-18

はじめに

Javaにて自分で作成したクラスのListから重複した要素を除外する方法について、いつもやり方を探してしまうので個人的なメモとしてまとめてみます。

前提

こんな感じのPersonクラスがあったとします。

Person.java
public class Person {
    private String id;
    private String name;
    private int age;

    public Person(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return this.id;
    }

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

    public int getAge() {
        return this.age;
    }

    // 確認用
    @Override
    public String toString() {
        return "[" + this.id + ", " + this.name + ", " + this.age + "]";
    }
}

下記のような形でPersonクラスのリストがあります。
そのリストの中から重複した要素を除外したいというケースになります。

Sample.java

public class Sample {
    public static void main(String[] args) {
        List<Person> personList = Arrays.asList(
                new Person("00001", "テスト太郎", 32),
                new Person("00002", "テスト次郎", 27),
                new Person("00003", "テスト三郎", 25),
                new Person("00001", "テスト太郎", 32),
                new Person("00003", "テスト三郎", 25),
                new Person("00001", "テスト太郎", 36)
        );

        List<Person> result = deduplicate(personList);

        // 実行結果の確認
        System.out.println(result.toString());
    }

    /**
     * 重複した要素を除外して返却する。
     * @param personList
     * @return
     */
    public static List<Person> deduplicate(List<Person> personList) {
        // ここに処理を書く!
        // ほげほげ
        return personList;
    }
}

方法1:equals()とhashCode()をOverrideしてStream や HashMapを使う

対象のクラスにてequals()hashCode()をOverrideします。

Person.java
public class Person {

    // 省略

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj instanceof Person) {
            Person p = (Person) obj;
            return p.id.equals(this.id) && p.name.equals(this.name) && p.age == this.age;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (this.id + this.name + this.age).hashCode();
    }
}

上記をOverrideすることで、Stream APIdistinct()で重複除外ができます。

Sample.java
    public static List<Person> deduplicate(List<Person> personList) {
        return personList.stream().distinct().collect(Collectors.toList());
    }
実行結果
[[00001, テスト太郎, 32], [00002, テスト次郎, 27], [00003, テスト三郎, 25], [00001, テスト太郎, 36]]

または、一度HashSetとすることで、重複要素の除外ができます。(順序を変更したくない場合はLinkedHashSetを使用します。)

Sample.java
    public static List<Person> deduplicate(List<Person> personList) {
        return new ArrayList<>(new LinkedHashSet<>(personList));
    }

方法2:適当なキーを指定してMapに入れる

上記の方法が使えないケースもあるかと思います。

  • equalshashCodeのOverrideができない、したくない。(なんかプロジェクトの問題とかで)
  • クラスの中の一部のプロパティだけをキーにしたい

そんなときは以下で対応できるかもしれません。

Sample.java
    public static List<Person> deduplicate(List<Person> personList) {
        Map<String, Person> map = new HashMap<>();
        for (Person person : personList) {
            // IDと名前だけをキーにする
            String key = person.getId() +"_" + person.getName();

            // 上記のキーで要素を格納
            map.put(key, person);
        }
        // MapをListに変換
        return new ArrayList<>(map.values());
    }

上記はIDと名前のみをキーとした場合の例です。

IDと名前を結合した文字列をMapのキーとして使用します。
同キーであれば上書きされます。(リストの後にある要素が勝ちます。)
なので実行結果は下記の通りとなります。

実行結果
[[00001, テスト太郎, 36], [00002, テスト次郎, 27], [00003, テスト三郎, 25]]

36歳の方のテスト太郎が残りました。

反対に先に来た方を残したい場合は、同キーの要素をcontinueでスキップします。

Sample.java
    public static List<Person> deduplicate(List<Person> personList) {
        Map<String, Person> map = new HashMap<>();
        for (Person person : personList) {
            // IDと名称だけをキーにする
            String key = person.getId() +"_" + person.getName();

            // 同じキーはスキップ
            if(map.containsKey(key)) {
                continue;
            }

            // 上記のキーで要素を格納
            map.put(key, person);
        }
        // MapをListに変換
        return new ArrayList<>(map.values());
    }

なんか自分が知らないだけでStream APIに使えそうなメソッドがありそうな気がしないでもない。

[追記]方法3:Comparatorを定義してTreeSetに詰め込む

コメントにて@saka1029さんよりご教示いただいた方法です。
Comparatorを定義し、TreeSetに詰め込む方法になります。
Comparatorに定義したプロパティをキーとして重複した要素を除外します。
※重複した場合、リストの先頭に近い方の要素が残ります。

Sample.java
    public static List<Person> deduplicate(List<Person> personList) {
        // Comparatorを定義
        Comparator<Person> personComparator = Comparator
                .comparing(Person::getId)
                .thenComparing(Person::getName);
        // TreeSetに詰め込む
        Collection<Person> personCollection = personList.stream()
                .collect(Collectors.toCollection(() -> new TreeSet<>(personComparator)));

        // [[00001, テスト太郎, 32], [00002, テスト次郎, 27], [00003, テスト三郎, 25]]
        return new ArrayList<>(personCollection);
    }

終わりに。

なんか他に良さげな方法がありましたらコメントでご教示いただければ幸いです!

2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2