疲れたらやすむ

Javaを学ぶ上でハマったところを書いていきます。iPhoneアプリ開発や日常ネタもあるかも。

【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でも出題される範囲なのでぜひ押さえておきたいところです。