比较 returns 0 时了解 TreeSet

Understanding TreeSet when compareto returns 0

我创建了一个这样的学生 class:

public class Student implements Comparable<Student> {

    private String firstName;
    private String lastName;

    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Getters & Setters follow here...

    @Override
    public int compareTo(Student student) {
        int hash = this.firstName.compareTo(student.firstName);
        return hash;
    }

    @Override
    public String toString() {
        return "Student [firstName=" + firstName + ", lastName=" + lastName
                + "]";
    }

}

这是我的测试 class,我只是将元素添加到我的 TreeSet 中:

public class SortedSetExample1 {
    public static void main(String[] args) {
        SortedSet<Student> set = new TreeSet<Student>();
        set.add(new Student("A1","A2"));
        set.add(new Student("B1","B2"));
        set.add(new Student("A1","B2"));
        set.add(new Student("A2","B2"));
        System.out.println(set);
    }
}

根据我的程序,输出是:

[Student [firstName=A1, lastName=A2], Student [firstName=A2, lastName=B2], Student [firstName=B1, lastName=B2]]

在我的测试 class 中,我将 Student 个对象添加到 TreeSet,而且我没有覆盖 hashCodeequals 方法。所以我期待 TreeSet 将包含所有 4 个对象,但我也可以看到它包含 3 个对象。你能解释一下为什么 new Student("A1","B2") 不是我的 TreeSet 的一部分吗?

此外,根据此处的 Java docs for TreeSet,它表示:

Adds the specified element to this set if it is not already present. More formally, adds the specified element e to this set if the set contains no element e2 such that (e==null ? e2==null : e.equals(e2)). If this set already contains the element, the call leaves the set unchanged and returns false.

因为我没有覆盖 equals 方法,那么为什么集合没有包含所有四个元素?

正如java.util.TreeSet所说:

a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal

感谢@Jon Skeet

那么,您的树集键值是 "A1"、"B1"、"A1"、"A2"。即使您没有覆盖 equals 和哈希码,"A1" 的默认哈希码仍然相同,因此树集会将此键视为重复键,因此您将无法输入 "A1"、"B2"

那是因为TreeSet使用compareTo(或Comparator.compare)来测试两个元素是否相等。这就是文档所说的。

Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.

由于您在 compareTo 方法中只比较了名字,因此您需要

 @Override
public int compareTo(Student student) {
    int comp = this.firstName.compareTo(student.firstName);
    if(comp==0) return this.lastName.compareTo(student.lastName);
    return comp;
}

当 compareTo returns 0 时,treeSet 假定它是重复的。

虽然问题很老,但这是一个非常重要的理解点,大多数人在实施时都忽略了这一点。

TreeSet 不会将新元素与 Set 中的所有现有元素进行比较。它使用二进制搜索技术。因此,如果您的输入元素等于现有元素(根据 compareTo 合同)并且相同位于 tree 的左侧,并且您的 compareTo 方法是这样实现的强制您的新元素位于 tree 的右侧,您的 TreeSet 不会拒绝新元素,即使其中已经存在相同的元素(根据 compareTo 合同)。让我们看下面的简单例子。

我需要对具有 keypriority 属性的 Item 进行排序。

package com.manish;
import java.util.TreeSet;
public class Main {
    static class Item implements Comparable<Item> {
        private long key;
        private int priority;
        public Item(long key, int priority) {
            super();
            this.key = key;
            this.priority = priority;
        }

        /*
         * Items should be treated equal if Keys are equal.
         * Higher priority item should be treated as greater item.
         * If priorities are same, lower key value item should be
         * treated as greater item.
         */
        @Override
        public int compareTo(Item o) {
            if (this.key == o.key) {
                return 0;
            }
            if (this.priority != o.priority) {
                return this.priority - o.priority;
            } else {
                return this.key < o.key ? 1 : -1;
            }
        }
        @Override
        public String toString() {
            return "Item [key=" + key + ", priority=" + priority + "]";
        }
    }
    public static void main(String[] args) {
        TreeSet<Item> set = new TreeSet<>();
        set.add(new Item(2, 1));
        set.add(new Item(4, 3));
        set.add(new Item(3, 1)); //line 1
        set.add(new Item(3, 2)); //line 2. Same item as Item(3,1)
        while (!set.isEmpty())
            System.out.println(set.pollFirst());
    }
}

输出:

Item [key=3, priority=1]
Item [key=2, priority=1]
Item [key=3, priority=2]
Item [key=4, priority=3]

但是,如果交换 line 1line 2 代码,输出将发生如下变化。

Item [key=2, priority=1]
Item [key=3, priority=2]
Item [key=4, priority=3]