自定义 Java 类型的 TreeMapTreeSet
由于 TreeMap
s 和 TreeSet
s 根据其自然顺序维护键/元素。因此,TreeMap
键和 TreeSet
元素必须相互比较。
假设我们有一个自定义 Person
类:
public class Person {
private int id;
private String firstName, lastName;
private Date birthday;
//... Constuctors, getters, setters and various methods
}
如果我们将它原样存储在 TreeSet
(或 TreeMap 中的 Key)中:
TreeSet<Person2> set = ...
set.add(new Person(1,"first","last",Date.from(Instant.now())));
然后我们会遇到像这样的异常:
Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at java.util.TreeSet.add(TreeSet.java:255)
为了解决这个问题,我们假设我们想要根据其 id(private int id
)的顺序来命令 Person
实例。我们可以通过以下两种方式之一来实现:
-
一种解决方案是修改
Person
,以便实现 Comparable 接口 :public class Person implements Comparable<Person> { private int id; private String firstName, lastName; private Date birthday; //... Constuctors, getters, setters and various methods @Override public int compareTo(Person o) { return Integer.compare(this.id, o.id); //Compare by id } }
-
另一个解决方案是为
TreeSet
提供一个比较器 :
Version >= Java SE 8
TreeSet<Person> treeSet = new TreeSet<>((personA, personB) -> Integer.compare(personA.getId(), personB.getId()));
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>(){
@Override
public int compare(Person personA, Person personB) {
return Integer.compare(personA.getId(), personB.getId());
}
});
但是,这两种方法都有两点需要注意:
-
将实例插入
TreeSet
/TreeMap
后,不要修改用于排序的任何字段,这一点非常重要。在上面的示例中,如果我们更改已插入集合的人员的id
,我们可能会遇到意外行为。 -
正确和一致地实施比较非常重要。根据 Javadoc :
实现者必须确保所有 x 和 y 的
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
。 (这意味着如果y.compareTo(x)
抛出异常,x.compareTo(y)
必须抛出异常。)实现者还必须确保关系是可传递的:
(
x.compareTo(y)>0 &&
y.compareTo(z)>0)
暗示了x.compareTo(z)>0
。最后,实现者必须确保
x.compareTo(y)==0
暗示sgn(x.compareTo(z)) == sgn(y.compareTo(z))
,对于所有 z。