根据 TreeSet,两个对象显示为相等,但 Queue 显示它们不相等

As per TreeSet two objects are shown equal but Queue shows them as unequal

我有以下人 Class -

Person.java -

public class Person implements Comparable<Person> {
    private int id;
    private String name;

    Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Person: Id = " + id + ", Name = " + name;
    }

    @Override
    public int compareTo(Person person) {
        int myReturn = 0;
        int minLength = 0;
        int i = 0;
        boolean equal = false;
        if (id > person.id) {
            myReturn = 1;
        } else if (id < person.id) {
            myReturn = -1;
        } else {
            if (name.length() > person.name.length()) {
                minLength = person.name.length();
            } else if (name.length() < person.name.length()) {
                minLength = name.length();
            } else {
                equal = true;
                minLength = name.length();
            }
            for (i = 0; i < minLength; i++) {
                if (name.charAt(i) > person.name.charAt(i))  {
                    myReturn = 1;
                    break;
                } else if (name.charAt(i) < person.name.charAt(i)) {
                    myReturn = -1;
                    break;
                } else {
                    continue;
                }
            }
            if (i == minLength) {
                if (equal) {
                    myReturn = 0;
                } else if (name.length() > person.name.length()) {
                    myReturn = 1;
                } else {
                    myReturn = -1;
                }
            }
        }
        return myReturn;
    }
}

现在,我有以下 TreeClass 实例 -

TreeSet<Person> treeSet = new TreeSet<>(List.of(
                new Person(4, "Amrita"),
                new Person(4, "Amrita"),
                new Person(9, "Sunita"),
                new Person(12, "Nisha"),
                new Person(9, "Sunit"),
                new Person(9, "Sunitaa")
        ));

打印时 -

Person: Id = 4, Name = Amrita
Person: Id = 9, Name = Sunit
Person: Id = 9, Name = Sunita
Person: Id = 9, Name = Sunitaa
Person: Id = 12, Name = Nisha

很明显,两个 Person 实例 - new Person(4, "Amrita")new Person(4, "Amrita") 是相等的。

现在,我有以下队列代码。因为,Queue 是 Collection 接口的子接口,它实现了 Collection 接口的所有方法。所以-

    Queue<Person> queue3 = new LinkedList<>(List.of(
                new Person(4, "Amrita"),
                new Person(2, "Suhana"),
                new Person(7, "Neha")
        ));
        Person person1 = new Person(4, "Amrita");
        Person person2 = new Person(9, "Sunita");
        System.out.println(queue3.contains(person1));
        System.out.println(queue3.contains(person2));

输出-

    false
    false

因此它说 new Person(4, "Amrita") 元素的队列和对象 new Person(4, "Amrita") 是不相等的。

这怎么可能?

您需要覆盖 class Person 中的 @equals 方法。参见 documentation

此外,我看到 Person 实现了 Comparable(TreeSet 使用 compareTo 来表示相等)。每当你@overridecompareTo(),强烈推荐@overide@equals.

来自 Collections Documentation:

强烈建议(虽然不是必需的)自然排序与等号一致。之所以如此,是因为没有显式比较器的排序集(和排序映射)在与自然顺序与 equals 不一致的元素(或键)一起使用时表现得“奇怪”。

长话短说,因为您正在使用 TreeSet 以及其他集合 (List),您需要 @override 所有三个 - compareTo, equals and hashCode

PS:无论何时覆盖 @equals - 请也 @override hashCode。参见 this

您可以在 IDE(Intellij / Eclipse/ VsCode 等)中自动生成这些方法。例如。它会是这样的:

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

TreeSet独占使用比较器判断是否相等。 comparator.compare(a, b) returns 0 的任何 2 个条目被视为相等。 equalshashCode 都没有被调用。

大多数其他集合 独占 仅使用 equalsequalshashCode 的组合来确定这一点。

因此,如果你写了一个class,它的equals、hashCode和compare方法不一致,你可以创造你所描述的情况:TreeSet认为它们相等但是例如HashSet 没有。

解决方案是正确应用各种 java.util classes:java文档中列出的规则:

  • a.equals(b)?然后,b.equals(a) 也必须成立,无论类型如何。
  • a.equals(a)必须为真。
  • a.equals(anything) 不能抛出任何异常,除非 anull 时的强制 NPE。 a.hashCode().
  • 相同
  • a.equals(null)一定是假的。
  • a.equals(b)b.equals(c)?那么a.equals(c)也一定为真。
  • a.equals(b)?那么 a.hashCode() == b.hashCode() 也一定为真。反过来不成立(相等的哈希码?这并不意味着相等的对象)。
  • a.compare(b)低于0?那么b.compare(a)一定在0以上。
  • a.compare(a) 必须为 0。
  • a.compare(b) == 0 必须与 a.equals(b) 匹配,如果一个为真,另一个必须为真,反之亦然。
  • a.compare(b) < 0b.compare(c) < 0?然后 a.compare(c) 也必须小于 0。与大于 0 相同。
  • equalshashCode 不同,compare 允许抛出多个异常。

很多规则。它们很容易理解(近乎显而易见),但很难正确应用,尤其是当您涉及 java 具有类型层次结构的想法时。

简单的例子:

ArrayList 有一个 equals impl,它将检查提供的对象是否是任何类型的列表,如果是,它只会进行 elem-by-elem 比较。

这意味着您不能编写实现 List 的 class 并添加任何类型的 equality-affecting 属性,期间 .

比如这个class:

class ColouredList<T> extends ArrayList<T> {
  private Color color;

  public ColouredList(Color color) {
    this.color = color;
  }

  // and so on
}

无法写入 - 除非你完全无视颜色,例如一个空的蓝色列表被认为等于一个空的红色列表。因为 emptyPlainArrayList.equals(blueEmptyList) 是真的,你不能让它成为假(因为你不控制 ArrayList 的 equals impl),所以 emptyPlainArrayList.equals(redEmptyList),因此,blueEmptyList.equals(redEmptyList) 也必须是真的.

此规则是规则集的逻辑结果,但并不明显,实际上认为您可以这样做是一个有点常见的错误。

因此,简单、几乎显而易见的规则组合在一起,构成了一个比您想象的要复杂得多的系统。

您违反了其中一项规则;根据您粘贴的内容,看起来很简单:您向 class 添加了自然比较顺序,但未能实现 hashCode 和 equals。您必须实施这些方法,以遵循 a.equals(b)a.compare(b) == 0 必须成立的规则(并且您必须实施 hashCode 以遵循“如果 a.equals(b) 然后你必须确保 a.hashCode() == b.hashCode().

我建议 Project Lombok 或您的 IDE 的“生成等号和哈希码”选项。这些方法也可能有点难以正确编写。