为什么TreeSet会抛出ClassCastException异常?

22

我正在尝试将两个“员工”对象添加到TreeSet中:

Set<Employee> s = new TreeSet<Employee>();
s.add(new Employee(1001));
s.add(new Employee(1002));

但是它会抛出一个 ClassCastException 异常:

Exception in thread "main" java.lang.ClassCastException: Employee cannot be cast to java.lang.Comparable
    at java.util.TreeMap.put(TreeMap.java:542)
    at java.util.TreeSet.add(TreeSet.java:238)
    at MyClient.main(MyClient.java:9)

但是,如果我只向TreeSet中添加一个对象:

Set<Employee> s = new TreeSet<Employee>();
s.add(new Employee(1001));

如果我使用 HashSet:

Set<Employee> s = new HashSet<Employee>();
s.add(new Employee(1001));
s.add(new Employee(1002));

然后它成功了。为什么会出现异常,我该如何解决?


3
你需要相信错误信息是正确的。问题出在“Employee不能转换成java.lang.Comparable”上。当你只有一个元素时,没有什么可以比较的,所以它检测不出问题。HashSet不使用Comparable,因此也不会对其进行检查。 - Peter Lawrey
6个回答

26
要么 Employee 实现 Comparable 接口,要么在创建 TreeSet 时提供一个比较器 (provide a comparator)
文档中详细说明了这一点,参见 SortedSet

所有插入有序集合的元素必须实现 Comparable 接口(或被指定比较器所接受)。此外,所有这些元素都必须可以相互比较:e1.compareTo(e2)(或者 comparator.compare(e1, e2))对于有序集合中的任何元素 e1e2 都不能抛出 ClassCastException。试图违反此限制将导致调用该方法或构造函数时抛出 ClassCastException

如果不满足这些要求,排序集合就不知道如何比较它的元素,并且无法正常工作。

好的,所以要么Employee必须实现comparable接口,要么我需要创建一个外部比较器,是吗? - Rais Alam
但在当前情况下,Employee类没有实现可比较接口,也没有外部比较器。当我添加一个元素时,一切都正常工作。但是当我添加第二个元素时,问题就开始了。问题是,如果您所描述的情况是强制性的,那么为什么编译器会编译该程序呢? - Rais Alam
@FireFly:这是强制性的,但只有在集合实际需要比较元素时才会执行(当您尝试添加第二个元素时发生)。 - NPE
@FireFly:没有外部比较器,该类假设特化参数实现了Comparable接口。两者必须存在,但编译器无法强制执行这样的约束条件,因为在编译时无法进行。 - LeleDumbo
@NPE 感谢您的答案解释。我在面试中被问到了这个问题,但未能回答。 - vijayraj34

3

TreeSet要求元素实现Comparable接口,如果没有设置自定义的Comparator。而HashSet则使用equals/hashCode协定。

如果一个元素不需要与其他元素进行比较,那么只能向TreeSet中添加一个未实现Comparable的元素。

查看TreeMap.put(K key, V value)源代码,您将清楚地看到所有问题的原因(TreeSet基于TreeMap,因此引用了源代码)。


1

TreeSet#add(E) JavaDoc 中:

Throws: ClassCastException - 如果指定的对象无法与当前集合中的元素进行比较

基本上你需要让 Employee 实现 Comparable 或者为 TreeSet 对象提供一个 Comparator

如果你查看 TreeMap 代码,你会发现如果在 Map 对象中找不到比较器,它将尝试直接将键(即你的 Employee 对象)强制转换为 Comparator

...
Comparable<? super K> k = (Comparable<? super K>) key;
...

0
//class Employee
    public class Employee implements Comparable<Employee>{
    int id;

    Employee(int id){
    this.id=id;
    }

    public int compareTo(Employee e){ //implementing abstract method.
    if(id>e.id){
    return 1;
    }
    return 0;
    }


//class TreeSet

    Set<Employee> emp =new TreeSet<Employee>();

    Employee eobj1 = new Employee(2);
    Employee eobj2 = new Employee(3);
    emp.add(eobj1);
    emp.add(eobj2);

    for (Student ss:emp) {
    System.out.println(ss.rollno);
    }
}
//output: 2
//        3

员工 eobj1 = 新 员工(3); 员工 eobj2 = 新 员工(2); //输出: 仅为 3 - charu

0

TreeSetSortedSet 的一种实现。您可以让 Employee 实现 Comparable 接口,或为您的 TreeSet 提供适当的 Comparator

Set<Employee> s = new TreeSet<Employee>(new EmployeeComparator());

0

因为TreeSet想要保持元素排序,所以需要将Employee对象实现Comparable接口。


实现 comparable 接口是必须的吗? - Rais Alam
@FireFly:不是这样的。你可以提供一个比较器。请看我的回答。 - NPE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接