【Java】TreeSetで独自クラスを扱う
今回はTreeSetに的を絞って解説していきます。
Setとは?ListやMapとの違い
Setとはコレクションの1つで、重複した値を持たない特徴があります。
Main.java
import java.util.HashSet; import java.util.Set; public class Main { public static void main(String[] args) { Set<String> set = new HashSet<String>(); set.add("apple"); set.add("orange"); set.add("apple"); for(String s : set) { System.out.println(s); } } }
このように同じ値をaddした場合しても
実行結果
orange apple
重複している値は追加されていません。
Listはaddすれば問答無用で追加するのに対し、Setはaddしても重複する値があれば要素に追加されません。
Mapと比較してみると、重複する値を持たないと言う部分ではキーと似ているかもしれません。
ですが、Mapはキーに対して値を持っているため、どちらかと言えばListの方が似ているかも。
Setの種類について
Setの代表的なものとして、HashSetとTreeSetが存在します。
基本的な理解としては、
「HashSetは格納された要素をソートしないがnullを格納できる」
「TreeSetは格納された要素をソートするがnullは格納できない」
で良いかと思います。
他にもLinkedHashSetと言うものがありますが、全く使ったことがないのでよくわかっていません。
処理速度とかの話なんですかね。
TreeSetの使い方
TreeSetは格納された要素をソートするため、ソート順を決める必要があります。
IntegerやStringなどの基本的な型の場合はそのまま使用可能。
その理由は、IntegerやStringのクラス定義でComparable<T>インターフェースを実装し、compareToメソッドでソート方法を宣言しているからです。
Integerの場合は、数値が小さい順でソートされます。
Main.java
import java.util.Set; import java.util.TreeSet; public class Main { public static void main(String[] args) { Set<Integer> set = new TreeSet<Integer>(); set.add(3); set.add(1); set.add(2); for (Integer i : set) { System.out.println(i); } } }
実行結果
1 2 3
Stringの場合はアルファベット順。
Main.java
import java.util.Set; import java.util.TreeSet; public class Main { public static void main(String[] args) { Set<String> set = new TreeSet<String>(); set.add("B"); set.add("A"); set.add("C"); for (String s : set) { System.out.println(s); } } }
実行結果
A B C
独自クラスでTreeSetを扱う場合
まずソート順を指定しなかった場合どうなるか見てみます。
とりあえず独自クラスを用意します。
Student.java
public class Student { int id; String name; public Student(int id, String name) { this.id = id; this.name = name; } public int getId() { return this.id; } public String getName() { return this.name; } }
そしてそのままTreeSetに格納してみます。
Main.java
import java.util.Set; import java.util.TreeSet; public class Main { public static void main(String[] args) { Set<Student> set = new TreeSet<Student>(); set.add(new Student(2, "Jiro")); set.add(new Student(1, "Taro")); set.add(new Student(3, "Saburo")); for (Student s : set) { System.out.println(s.getId() + " : " + s.getName()); } } }
実行結果
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
はい。
ClassCastExceptionが発生します。
これは、TreeSetで要素を並び替える際にComparable<T>型にキャストを行う処理があるためです。
つまりキャストを行うには、独自クラスにComparable<T>を実装する必要があります。
Student.java
public class Student implements Comparable<Student> { int id; String name; public Student(int id, String name) { this.id = id; this.name = name; } public int getId() { return this.id; } public String getName() { return this.name; } @Override public int compareTo(Student o) { return this.id - o.id; } }
これで実行してみます。
実行結果
1 : Taro 2 : Jiro 3 : Saburo
ちゃんとソートされていますね。
ちなみに、独自クラス側でComparable<T>を実装しないパターンもあります。
その場合は、使用する側でソート順を指定します。
Set<Student> set = new TreeSet<Student>(Comparator.comparing(Student::getId));
これでStudentにComparable<T>が実装されていなくても、idの昇順で問題なく動作します。
関数型インターフェースやメソッド参照が出てきて少しややこしい部分はありますが。
このあたりはJava Goldでも出題される範囲なのでぜひ押さえておきたいところです。