はじめに
Javaにて自分で作成したクラスのListから重複した要素を除外する方法について、いつもやり方を探してしまうので個人的なメモとしてまとめてみます。
前提
こんな感じのPerson
クラスがあったとします。
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
クラスのリストがあります。
そのリストの中から重複した要素を除外したいというケースになります。
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します。
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 API
のdistinct()
で重複除外ができます。
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
を使用します。)
public static List<Person> deduplicate(List<Person> personList) {
return new ArrayList<>(new LinkedHashSet<>(personList));
}
方法2:適当なキーを指定してMapに入れる
上記の方法が使えないケースもあるかと思います。
-
equals
、hashCode
のOverrideができない、したくない。(なんかプロジェクトの問題とかで) - クラスの中の一部のプロパティだけをキーにしたい
そんなときは以下で対応できるかもしれません。
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
でスキップします。
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
に定義したプロパティをキーとして重複した要素を除外します。
※重複した場合、リストの先頭に近い方の要素が残ります。
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);
}
終わりに。
なんか他に良さげな方法がありましたらコメントでご教示いただければ幸いです!