比较器和 equals()

发布于 2024-10-06 00:12:52 字数 1271 浏览 3 评论 0 原文

假设我需要 TreeSet ,其中元素按某些域逻辑排序。按照这种逻辑,某些不相等的元素的顺序并不重要,因此比较方法可以返回 0,但在这种情况下,我无法将它们放入 TreeSet 中。

所以,问题是:这样的代码会有什么缺点:

class Foo implements Comparable<Foo>{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

更新

好的。是否应该始终保持方法 equals()hashcode()compareTo() 之间的一致性,如 @SPFloyd - Seanizer 等说。 如果我删除 Comparable 接口并将此逻辑移到 Comparator 中是否会更好甚至更好(我可以在不破坏封装的情况下做到这一点)?所以它将是:

class Foo{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        //some logic start
        if(strictliBigger(o1, o2)){ return 1;}
        if(strictliBigger(o2, o1)){ return -1;}
        //some logic end
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

更新2

如果我不需要稳定排序,System.identityHashCode(x)会比hashCode()更好吗?

Suppose I need TreeSet with elements sorted with some domain logic. By this logic it doesn't matter order of some elements that doesn't equal so compare method can return 0, but in this case I couldn't put them in TreeSet.

So, question: what disadvantages I'll have from code like this:

class Foo implements Comparable<Foo>{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

Update:

Ok. If it should always be a consistency between the methods equals(), hashcode() and compareTo(), as @S.P.Floyd - seanizer and others said.
If it would be better or even good if I'll remove Comparable interface and move this logic in Comparator (I can do it without broken encapsulation)? So it will be:

class Foo{}
new TreeSet<Foo>(new Comparator<Foo>(){
    @Override
    public int compare(Foo o1, Foo o2) {
        //some logic start
        if(strictliBigger(o1, o2)){ return 1;}
        if(strictliBigger(o2, o1)){ return -1;}
        //some logic end
        if(res == 0 || !o1.equals(o2)){
            return o1.hashCode() - o2.hashCode();
        }
        return res;
    }
});

Update 2:

Would System.identityHashCode(x) be better than hashCode() if I don't need stable sort?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

不寐倦长更 2024-10-13 00:12:52

虽然这可能有效,但它远不是最佳实践。

来自 SortedSet 文档

请注意,排序集维护的顺序(无论是否提供显式比较器)如果排序集要正确实现 Set 接口,则必须与 equals 一致。 (请参阅 Comparable 界面或 Comparator接口,用于与equals一致的精确定义。)之所以如此,是因为 Set 接口是根据 equals 操作定义的,但是有序集合使用其compareTo(或compare)方法执行所有元素比较,因此从有序集,相等。即使排序集的顺序与 equals 不一致,排序集的行为也是明确定义的;它只是没有遵守 Set 接口的一般契约。


对于实现Comparable的对象,equals()hashcode()compareTo()方法之间应该始终保持一致性


恐怕 SortedSet 不是您想要的,Guava MultiSet 也不够(因为它不会让您独立检索多个相等的项目)。我认为您需要的是一个 SortedList。据我所知,没有这样的野兽(也许在公共集合中,但这些有点遗留),所以我使用 Guava 的 ForwardingList 作为基类为您实现了一个。简而言之:此 List 将几乎所有内容委托给它内部使用的 ArrayList,但它在其 add() 方法中使用 Collections.binarySearch()找到正确的插入位置,并且它会在 ListListIterator 接口的所有可选方法上抛出 UnsupportedOperationException,这些方法在给定位置添加或设置值位置。

构造函数与 ArrayList,但对于每个版本,还有带有自定义 Comparator 的第二个版本。如果您不使用自定义比较器,则列表元素需要实现Comparable,否则在排序过程中将出现RuntimeException

public class SortedArrayList<E> extends ForwardingList<E> implements
    RandomAccess{

    private final class ListIteratorImpl extends ForwardingListIterator<E>{
        private final int start;
        public ListIteratorImpl(final int start){
            this.start = start;
        }

        @Override
        public void set(E element){throw new UnsupportedOperationException();}

        @Override
        public void add(E element){throw new UnsupportedOperationException();}

        @Override
        protected ListIterator<E> delegate(){return inner.listIterator(start);};

    }

    private Comparator<? super E> comparator;

    private List<E> inner;

    public SortedArrayList(){this(null, null, null);}

    @SuppressWarnings("unchecked")
    private SortedArrayList(
        final List<E> existing,
        final Collection<? extends E> values,
        final Comparator<? super E> comparator
    ){
        this.comparator =
            (Comparator<? super E>)
               (comparator == null
                   ? Ordering.natural()
                   : comparator   );
        inner = (
            existing == null
                ? (values == null
                      ? new ArrayList<E>(values)
                      : new ArrayList<E>()
                   )
                : existing;
    }

    public SortedArrayList(final Collection<? extends E> c){
        this(null, c, null);
    }

    public SortedArrayList(final Collection<? extends E> c,
        final Comparator<? super E> comparator){
        this(null, c, comparator);
    }

    public SortedArrayList(final Comparator<? super E> comparator){
        this(null, null, comparator);
    }

    public SortedArrayList(final int initialCapacity){
        this(new ArrayList<E>(initialCapacity), null, null);
    }

    public SortedArrayList(final int initialCapacity,
        final Comparator<? super E> comparator){
        this(new ArrayList<E>(initialCapacity), null, comparator);
    }

    @Override
    public boolean add(final E e){
        inner.add(
            Math.abs(
                Collections.binarySearch(inner, e, comparator)
            ) + 1,
            e
        );
        return true;
    }

    @Override
    public void add(int i, E e){throw new UnsupportedOperationException();}

    @Override
    public boolean addAll(final Collection<? extends E> collection){
        return standardAddAll(collection);
    }

    @Override
    public boolean addAll(int i,
        Collection<? extends E> es){
        throw new UnsupportedOperationException();
    }

    @Override
    protected List<E> delegate(){ return inner; }

    @Override
    public List<E> subList(final int fromIndex, final int toIndex){
        return new SortedArrayList<E>(
            inner.subList(fromIndex, toIndex),
            null,
            comparator
        );
    }

    @Override
    public ListIterator<E> listIterator(){ return new ListIteratorImpl(0); }

    @Override
    public ListIterator<E> listIterator(final int index){
        return new ListIteratorImpl(index);
    }

    @Override
    public E set(int i, E e){ throw new UnsupportedOperationException(); }

}

While this might work, it is far from being a best practice.

From the SortedSet docs:

Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals if the sorted set is to correctly implement the Set interface. (See the Comparable interface or Comparator interface 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 sorted set 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 sorted set, equal. The behavior of a sorted set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.

For objects that implement Comparable, there should always be a consistency between the methods equals(), hashcode() and compareTo().


I'm afraid a SortedSet is just not what you want, nor will a Guava MultiSet be adequate (because it will not let you independently retrieve multiple equal items). I think what you need is a SortedList. There is no such beast that I know of (maybe in commons-collections, but those are a bit on the legacy side), so I implemented one for you using Guava's ForwardingList as a base class. In short: this List delegates almost everything to an ArrayList it uses internally, but it uses Collections.binarySearch() in it's add() method to find the right insertion position and it throws an UnsupportedOperationException on all optional methods of the List and ListIterator interfaces that add or set values at a given position.

The Constructors are identical to those of ArrayList, but for each of them there is also a second version with a custom Comparator. If you don't use a custom Comparator, your list elements need to implement Comparable or RuntimeExceptions will occur during sorting.

public class SortedArrayList<E> extends ForwardingList<E> implements
    RandomAccess{

    private final class ListIteratorImpl extends ForwardingListIterator<E>{
        private final int start;
        public ListIteratorImpl(final int start){
            this.start = start;
        }

        @Override
        public void set(E element){throw new UnsupportedOperationException();}

        @Override
        public void add(E element){throw new UnsupportedOperationException();}

        @Override
        protected ListIterator<E> delegate(){return inner.listIterator(start);};

    }

    private Comparator<? super E> comparator;

    private List<E> inner;

    public SortedArrayList(){this(null, null, null);}

    @SuppressWarnings("unchecked")
    private SortedArrayList(
        final List<E> existing,
        final Collection<? extends E> values,
        final Comparator<? super E> comparator
    ){
        this.comparator =
            (Comparator<? super E>)
               (comparator == null
                   ? Ordering.natural()
                   : comparator   );
        inner = (
            existing == null
                ? (values == null
                      ? new ArrayList<E>(values)
                      : new ArrayList<E>()
                   )
                : existing;
    }

    public SortedArrayList(final Collection<? extends E> c){
        this(null, c, null);
    }

    public SortedArrayList(final Collection<? extends E> c,
        final Comparator<? super E> comparator){
        this(null, c, comparator);
    }

    public SortedArrayList(final Comparator<? super E> comparator){
        this(null, null, comparator);
    }

    public SortedArrayList(final int initialCapacity){
        this(new ArrayList<E>(initialCapacity), null, null);
    }

    public SortedArrayList(final int initialCapacity,
        final Comparator<? super E> comparator){
        this(new ArrayList<E>(initialCapacity), null, comparator);
    }

    @Override
    public boolean add(final E e){
        inner.add(
            Math.abs(
                Collections.binarySearch(inner, e, comparator)
            ) + 1,
            e
        );
        return true;
    }

    @Override
    public void add(int i, E e){throw new UnsupportedOperationException();}

    @Override
    public boolean addAll(final Collection<? extends E> collection){
        return standardAddAll(collection);
    }

    @Override
    public boolean addAll(int i,
        Collection<? extends E> es){
        throw new UnsupportedOperationException();
    }

    @Override
    protected List<E> delegate(){ return inner; }

    @Override
    public List<E> subList(final int fromIndex, final int toIndex){
        return new SortedArrayList<E>(
            inner.subList(fromIndex, toIndex),
            null,
            comparator
        );
    }

    @Override
    public ListIterator<E> listIterator(){ return new ListIteratorImpl(0); }

    @Override
    public ListIterator<E> listIterator(final int index){
        return new ListIteratorImpl(index);
    }

    @Override
    public E set(int i, E e){ throw new UnsupportedOperationException(); }

}
何止钟意 2024-10-13 00:12:52

注意:即使对于两个 Foos f1,f2f1 != f2,你也可能得到 f1.hashCode() == f2。哈希码()!这意味着您将无法使用 compare 方法获得稳定的排序。

Beware: even for two Foos f1,f2 with f1 != f2 you could get f1.hashCode() == f2.hashCode()! That means you won't get a stable sorting with your compare Method.

摘星┃星的人 2024-10-13 00:12:52

Java 中没有规则规定两个对象的哈希码必须不同,因为它们不相等(因此 o1.hashCode() - o2.hashCode() 可能返回 0 在你的情况下)。

此外,equals() 的行为应该compareTo() 的结果一致。这不是必须,但如果您无法维持这一点,则表明您的设计存在很大缺陷。

我强烈建议查看对象的其他字段并使用其中一些字段来扩展您的比较,以便您获得对象的值 != 0equals() == false.

There is no rule in Java which says that the hash codes of two objects must be different just because they aren't equal (so o1.hashCode() - o2.hashCode() could return 0 in your case).

Also the behavior of equals() should be consistent with the results from compareTo(). This is not a must but if you can't maintain this, it suggests that your design has a big flaw.

I strongly suggest to look at the other fields of the objects and use some of those to extend your comparison so you get a value != 0 for objects were equals() == false.

笑叹一世浮沉 2024-10-13 00:12:52

hashcode() 方法不保证任何小于大于compare()equals() 应该产生相同的含义,但这不是必需的。

据我从您令人困惑的代码中可以理解(无意冒犯:)),您想向 TreeSet 添加重复项。出于这个原因,您提出了这个实现。原因如下,您不能将它们放入 TreeSet 中,引用文档,

集合的行为是明确定义的
即使它的顺序不一致
与平等;它只是不遵守
Set接口的通用契约。

因此,您需要使用您的 equals() 方法做一些事情,这样它就永远不会返回 true 。最好的实现是,

public boolean equals(Object o) {
    return false;
}

顺便说一句,如果我的理解是正确的,为什么不使用 List 来代替并对其进行排序。

hashcode() method doesn't guarantee any less than or greater than. compare() and equals() should yield the same meaning, but its not necessary, though.

As far as I can understand from your confusing code (no offence intended :)), you want to add duplicates to the TreeSet. For that reason you came up with this implementation. Here is the reason, you can't put them in the TreeSet, quoting from the docs,

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.

So, you need to do something with yor equals() method, so it can never return true whats so ever. The best implementation would be,

public boolean equals(Object o) {
    return false;
}

By the way, if I am right in my understanding, why not you use List instead and sort that.

千紇 2024-10-13 00:12:52

非常有趣的问题。
据我了解,您的问题是重复元素。

我认为如果 o1.equals(o2) 它们的哈希码也可能相等。这取决于 Foo 类中 hashCode() 的实现。因此,我建议您改用 System.identityHashCode(x)。

Very interesting question.
As far as I understand your problem is duplicate elements.

I think that if o1.equals(o2) their hash codes might be equal too. It depends on the implementation of hashCode() in your Foo class. So, I'd suggest you to use System.identityHashCode(x) instead.

晨光如昨 2024-10-13 00:12:52

您有一个具有可比性的 Foo 类,但希望在 TreeSet 结构中使用不同的排序。那么你的想法就是正确的做法。使用该构造函数“否决”Foo 的自然排序。

You have a Foo class wich is comparable but want to use a different sorting in a TreeSet<Foo> structure. Then your idea is the correct way to do it. Use that constructor to "overrule" the natural sorting of Foo.

゛清羽墨安 2024-10-13 00:12:52

如果您对任何两个给定元素没有特定的预期顺序,但仍然希望将它们视为不相等,那么无论如何您都必须返回一些指定的顺序。

正如其他人所发布的, hashCode() 不是一个好的候选者,因为两个元素的 hashCode() 值很容易相等。 System.identityHashCode() 可能是一个更好的选择,但仍然不完美,因为即使 identityHashCode() 也不能保证唯一值

番石榴 Arbitration() Ordering 使用 Comparator >System.identityHashCode()。

If you have no specific expected ordering for any two given elements, but still want to consider them un-equal, then you have to return some specified ordering anyway.

As others have posted, hashCode() isn't a good candidate, because the hashCode() values of both elements can easily be equal. System.identityHashCode() might be a better choice, but still isn't perfect, as even identityHashCode() doesn't guarantee unique values either

The Guava arbitrary() Ordering implements a Comparator using System.identityHashCode().

今天小雨转甜 2024-10-13 00:12:52

是的,正如上面其他人所说,hashCode() 在这里使用并不安全。但是,如果您不关心在 o1.compareTo(o2) == 0 方面相等的对象的顺序,您可以执行以下操作:

public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if (res == 0 && !o1.equals(o2)) {
            return -1;
        }
        return res;
}

Yes, as others said above, hashCode() is not secure to use here. But if you dont care about the ordering of objects that are equal in terms of o1.compareTo(o2) == 0, you could do something like:

public int compare(Foo o1, Foo o2) {
        int res = o1.compareTo(o2);
        if (res == 0 && !o1.equals(o2)) {
            return -1;
        }
        return res;
}
债姬 2024-10-13 00:12:52
int res = o1.compareTo(o2);

if(res == 0 || !o1.equals(o2)){
    return o1.hashCode() - o2.hashCode();
}

可能会出现问题,因为如果两个对象相等(即在您的 res == 0 中),那么这两个对象将返回相同的哈希码。哈希码对于每个对象来说并不是唯一的。


编辑 @Stas,System.identityHashCode(Object x); 仍然无法帮助您。 javadoc上描述了原因:

返回相同的哈希码
给定的对象将返回
默认方法hashCode()
是否给定对象的
类重写hashCode()。哈希值
空引用的代码为零。

int res = o1.compareTo(o2);

if(res == 0 || !o1.equals(o2)){
    return o1.hashCode() - o2.hashCode();
}

Can be problematic, since if 2 objects are equal (i.e. in your res == 0) then these 2 objects return the same hashcode. Hashcodes are not unique for every object.


Edit @Stas, The System.identityHashCode(Object x); still won't help you. The reason is described on the javadoc:

Returns the same hash code for the
given object as would be returned by
the default method hashCode(),
whether or not the given object's
class overrides hashCode(). The hash
code for the null reference is zero.

陪你到最终 2024-10-13 00:12:52

这里有几个问题:

  • 哈希码通常不是唯一的,特别是 System.identityHashCode 在模糊现代的 JVM 上不会是唯一的。

  • 这不是稳定性问题。我们正在对数组进行排序,但创建一个树结构。哈希码冲突将导致 compare 返回零,这对于 TreeSet 意味着一个对象获胜,另一个对象被丢弃 - 它不会降级为链表(线索名称中带有“Set”)。

  • 从一个哈希码中减去另一个哈希码时通常会出现整数溢出问题。这意味着比较不会是传递的(即它被破坏)。幸运的是,在 Sun/Oracle 实现上,System.identityHashCode 始终返回正值。这意味着广泛的测试可能不会发现这种特定类型的错误。

我不认为有一个好的方法可以使用TreeSet来实现这一点。

There are a couple of problems here:

  • Hash codes are not generally unique, and in particular System.identityHashCode will not be unique on vaguely modern JVMs.

  • This is not a question of stability. We are sorting an array, but creating a tree structure. The hash code collisions will cause compare to return zero, which for TreeSet means one object wins and the other is discarded - it does not degrade to a linked-list (the clue is having "Set" in the name).

  • There is generally an integer overflow issue with subtracting one hash code from another. This means the comparison wont be transitive (i.e. it is broken). As luck would have it, on the Sun/Oracle implementation, System.identityHashCode always returns positive values. This means that extensive testing will probably not find this particular sort of bug.

I don't believe there is a good way to achieve this using TreeSet.

夏末 2024-10-13 00:12:52

有两点可能相关,即一种情况下的返回显示为 -1,这取决于函数参数变量或相关使用国家/地区是否允许负值,以及您使用的方法是否为允许的。有标准的数据排列方法,例如选择器或选择排序,并且如果您的工作场所没有副本,通常可以从国家当局获得论文描述或代码。使用大于或小于之类的比较可以加快代码速度,并通过隐式删除到后面的脚本或代码来避免使用直接比较相等性。

Two points may be relevant and these are that the return in one situation is shown as -1 and this depends if a negative value is permitted in the function parameter variable or in the relevant country of use, and also if the method you are using is permitted. There are standard data arranging methods like selector or selection sort and the paper description or code is usually available from a national authority if a copy is not in your workplace. Using comparisons like greater than or less than can speed up the code and avoids the use of a direct comparison for equality by implied dropthrough to later script or code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文