比较器和等号()

时间:2010-12-03 09:44:29

标签: java equals comparator treeset

假设我需要使用某些域逻辑排序的元素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()之间的一致性,正如@ S.P.Floyd - 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()好吗?

11 个答案:

答案 0 :(得分:9)

虽然这可行,但它远非最佳做法。

来自SortedSet docs

  

请注意,排序集维护的排序(无论是否提供显式比较器)必须与equals一致,如果排序集要正确实现Set接口。 (有关与equals一致的精确定义,请参阅Comparable接口或Comparator接口。)这是因为Set接口是根据equals操作定义的,但是有序集执行所有元素比较使用compareTo(或compare)方法,因此从排序集的角度来看,这种方法认为相等的两个元素是相等的。即使排序与equals不一致,排序集的行为也是明确定义的;它只是没有遵守Set接口的一般合同。

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


我担心SortedSet不是你想要的,番石榴MultiSet也不够(因为它不会让你独立检索多个相等的物品)。我认为你需要的是SortedList。我知道没有这样的野兽(也许在公共收藏中,但那些在传统方面有点),所以我使用Guava的ForwardingList作为基类为你实现了一个。简而言之:此列表几乎将所有内容委托给它在内部使用的ArrayList,但它在Collections.binarySearch()方法中使用add()来查找正确的插入位置,并抛出UnsupportedOperationException关于在给定位置添加或设置值的ListListIterator接口的所有可选方法。

构造函数与ArrayList的构造函数相同,但对于每个构造函数,还有第二个版本具有自定义Comparator。如果您不使用自定义Comparator,那么列表元素需要在排序过程中实现ComparableRuntimeException

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(); }

}

答案 1 :(得分:2)

hashcode()方法不保证任何less thangreater thancompare()equals()应该产生相同的含义,但不是必需的。

据我所知,您可以从令人困惑的代码中理解(无意犯罪:)),您希望向TreeSet添加重复项。出于这个原因,你想出了这个实现。这就是原因,你不能把它们放在TreeSet中,引用文档,

  

集合的行为是明确定义的   即使它的排序不一致   与...平等它只是没有服从   Set接口的一般合约。

所以,你需要用yor equals()方法做一些事情,所以它永远不会返回真正的事情。最好的实施方式是,

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

顺便说一句,如果我的理解是正确的,为什么不使用List代替它呢。

答案 2 :(得分:2)

注意:即使两个Foos f1f2f1 != f2,你也可以获得f1.hashCode() == f2.hashCode()!这意味着您无法使用compare方法获得稳定的排序。

答案 3 :(得分:2)

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

此外,equals() 的行为应与compareTo()的结果一致。这不是必须但如果你不能保持这个,那就表明你的设计有一个很大的缺陷。

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

答案 4 :(得分:1)

非常有趣的问题。 据我所知,你的问题是重复的元素。

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

答案 5 :(得分:1)

你有一个Foo类可比,但想在TreeSet<Foo>结构中使用不同的排序。那你的想法是正确的方法。使用该构造函数“否决”Foo的自然排序。

答案 6 :(得分:1)

int res = o1.compareTo(o2);

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

可能有问题,因为如果2个对象相等(即在res == 0中),那么这2个对象将返回相同的哈希码。 Hashcodes并非每个对象都是唯一的。


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

  

返回相同的哈希码   给定的对象将被返回   默认方法hashCode(),   是否给定的对象   class覆盖hashCode()。哈希   空引用的代码为零。

答案 7 :(得分:1)

如果您对任何两个给定元素没有特定的预期排序,但仍想将它们视为不相等,那么您还是必须返回一些指定的排序。

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

Guava arbitrary() Ordering使用Comparator实现System.identityHashCode()

答案 8 :(得分:1)

是的,正如其他人所说,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;
}

答案 9 :(得分:1)

这里有几个问题:

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

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

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

我不相信有一种很好的方法可以使用TreeSet来实现这一点。

答案 10 :(得分:1)

两点可能是相关的,这些是在一种情况下的返回显示为-1,这取决于在函数参数变量或相关的使用国家中是否允许负值,以及如果方法是正在使用是允许的。有标准数据安排方法,如选择器或选择排序,如果副本不在您的工作场所,通常可以从国家机构获得纸质描述或代码。使用大于或小于大小的比较可以加快代码速度,并避免使用对后续脚本或代码的隐含穿透来直接比较相等性。