TreeSetに登録したはずのデータが消えた話

Java小ネタ
この記事は約5分で読めます。

この記事の目的

同じ要素を排除した一覧をつくるときはSetインターフェースを使うのが便利です。Setインターフェースの実装クラスであるTreeSetクラスで思いがけない挙動を見つけたのでご紹介します。

「同じ」ことをどう判断するかがポイントです。

サンプルプログラム

名前(name)と年齢(age)をメンバ変数にもつHumanクラスを考えます。

import lombok.Value;

// lombokによってgetNameメソッド、getAgeメソッドが実装される
// equalsメソッドもOverrideされ、nameとageが両方等しいときのみtrueを返す
@Value
public class Human {
	private final String name;
	private final int age;
}

年齢の若い順に並び替えるComparatorを渡してTreeSetを作成します。Setインターフェースで一般に使われるHashSetとの違いは、「要素の並び替えを行う機能がついていること」です。

年齢は同じですが名前の異なる2人をTreeSetに登録し、一覧を出力します。

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetAddTestMain {
	// 年齢の若い順に並び替え
	private static final Comparator<Human> COMPARATOR =
			Comparator.comparing(Human::getAge);

	public static void main(String[] args) {
		Set<Human> set = new TreeSet<>(COMPARATOR);
		set.add(new Human("Taro", 20));
		set.add(new Human("Hanako", 20));
		set.stream().forEach(System.out::println);
	}
}

TaroとHanakoの2人が出力されそうですが、期待に反してTaroしか出力されません。

Human(name=Taro, age=20)

どうすれば期待通り動くのか

並び替えのルールを変えれば期待通り動きます。年齢が同じときは名前の辞書順で並び替えるようにしてみます。

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetAddTestMain {
	// 年齢の若い順に並び替え、年齢が同じときは名前の辞書順で並び替え
	private static final Comparator<Human> COMPARATOR =
			Comparator.comparing(Human::getAge)
			.thenComparing(Human::getName);

	public static void main(String[] args) {
		Set<Human> set = new TreeSet<>(COMPARATOR);
		set.add(new Human("Taro", 20));
		set.add(new Human("Hanako", 20));
		set.stream().forEach(System.out::println);
	}
}

今度はTaroとHanakoの2人が出力されます。HとTではHのほうが辞書順が先なので、Hanakoが先に出力されます。

Human(name=Hanako, age=20)
Human(name=Taro, age=20)

期待に反する挙動になる理由

Setインターフェースは通常、「同じ」ことの判定をequalsメソッドで行います。

Set (Java Platform SE 8)

一方、TreeSetクラスは「同じ」ことの判定をcompareTo/compareメソッドで行います。つまり並び替えの順序が同じであれば、他に何か違いがあっても同じものと扱ってしまうということです。

TreeSetインスタンスはそのcompareToメソッドまたはcompareメソッドを使用してすべての要素比較を実行するので、・・・(中略)・・・Setインタフェースの一般規約には準拠していません。

出典: docs.oracle.com

まとめ

TreeSetクラスの思いがけない挙動をご紹介しました。このような現象に遭遇すると、テストをすることの大切さが分かりますね。

コメント

タイトルとURLをコピーしました