在PriorityQueues中compareTo和equals

时间:2011-06-25 14:00:29

标签: java equals compareto

我对所有的“有点混淆”如果C对S施加的排序与equals不一致,那么排序的集合(或有序映射)将表现得很奇怪。“ Javadoc中的警告。 我甚至不确定PriorityQueue是否是我需要的......

我的情况是这样的: 我有一个带有整数时间戳和其他一些字段的类Event。 我正在寻找一个数据结构,我可以在其中插入这些事件,并按时间戳对事件进行排序。 不同的事件可以具有相同的时间戳,因此 - 如果我理解正确 - compareTo和equals会不一致。

我的第一个方法是让Event实现Comparable并提供compareTo,如下所示: public int compareTo(Event e){   return this.timestamp - e.getTimestamp(); }

我不明白我应该如何解决这个问题。我考虑过创建一个自定义Comparator,但同样的警告也会出现在Comparator的javadoc中。 我不想插入多个相等的事件实例,我只是希望它们按时间戳排序。

提前感谢您的帮助:)

编辑:
我只是希望事件按时间戳排序。很可能,两个不同的事件具有相同的时间戳。因此compareTo将返回0,因为它们具有相同的时间戳并且相等于排序目的。但是equals()不会返回true,因为它们是不同的事件 我不确定,PriorityQueue是正确的使用方法。我查看了SortedSet,但它对compareTo和equals的一致性有相同的警告 也许我是从错误的角度解决这个问题,我不知道......

3 个答案:

答案 0 :(得分:5)

  

不同的事件可以具有相同的时间戳

     

按时间戳

对事件进行排序

后一项要求有些不明确。 Collection的迭代器应该按排序顺序返回实例吗?或者,如果循环中的poll(),集合是否应按排序顺序返回其以前的内容?

iterator()按顺序返回元素

PriorityQueue不是这种情况。您可以使用SortedSet,但那些要求排序顺序与equals一致,正如您正确指出的那样,您无法实现。据我所知,JDK中没有Collection将其元素按排序顺序保持排序顺序,以使某些元素相等。但是,您可以使用数组或ArrayList,并在使用Arrays.sortCollection.sort进行更改后手动对其进行排序。如果集合很少变化,这就是我选择的方法。如果它经常更改,您将不得不超越JDK或自己实现数据结构。

poll()按排序顺序返回元素

这就是优先队列的好处。 PriorityQueue Comparator不要求Comparable(或PriorityQueue的实现)与equals一致;它的JavaDoc清楚地写道:

  

此队列的 head 是关于指定排序的 least 元素。如果多个元素被绑定为最小值,那么头部就是其中一个元素 - 关系被任意打破。

此外,JDK 6中equals的实现仅使用indexOf(E)来实现contains(Object)remove(Object)Collection,两者都不使用比较器无论如何。因此,对于这个SortedSet而言,与equals的一致性确实无关紧要。

可比较与比较者

请注意,就与equals的一致性而言,是否实现Comparable或Comparator并不重要。对于PriorityQueue,要么必须与等号一致,对于Collection.sortArrays.sortTreeSet,两者都不一定。

equalsTreeSet

的一致性

取消评论:

  

Set是一个SortedSet,并明确声明只依赖compareTo / compare。它明确地说:“即使它的排序与equals不一致,集合的行为也是明确定义的;它只是不遵守Set接口的一般契约。”

如果您引用,请引用所有相关部分。整段写着:

  

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

所以是的,它定义明确,但它没有做出问题所要求的:如果你传递Event EventEvent中的另一个Event相同的时间戳设置后,即使equal不是Collection,新Events也会被视为重复且未添加。问题是关于排序{{1}};这不应该消除复制排序键的{{1}}吗?

答案 1 :(得分:4)

  

如果c对S施加的顺序与equals不一致,则排序集(或有序映射)的行为会很奇怪。

这仅仅意味着当且仅当e1.equals(e2)然后e1.compareTo(e2) == 0

当且仅当!e1.equals(e2)然后e1.compareTo(e2) != 0

这是使两种方法保持一致所必须做的事情。

因此,通过实现compareTo的方式,您还应该将equals()重写为:

@Override
public boolean equals(Event e) {
    return this.timestamp.equals(e.timestamp);
}

注意:我不知道时间戳的数据类型,但如果它是基本类型,则使用==而不是equals()作为重写方法。

答案 2 :(得分:0)

当您实施Comparable时,您还应该覆盖equals(Object),因为compareTo当且仅当equals返回true时才应返回零。

  

compareTo(T)只应在且仅当equals(Object)返回true时才返回零。

并非全部。由于另一个合同,当您覆盖hashCode()时,您应该/必须覆盖equals

  

Equal对象必须具有相同的哈希码。

public class Event implements Comparable<Event> {

    private long timestamp;

    public long getTimestamp() {
        return this.timestamp;
    }

    @Override
    public int compareTo(Event o) {
        return (this.timestamp < o.timestamp ? -1
                : (this.timestamp == o.timestamp ? 0 : 1));
    }

    @Override
    public int hashCode() {
        return (int) (this.timestamp ^ (this.timestamp >>> 32));
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Event) {
            return this.timestamp == ((Event) obj).timestamp;
        }

        return false;
    }

}

compareToequalshashCode实施取自您在java.lang.Long中可以看到的实施。您也可以通过像Eclipse这样的IDE生成这些方法。

如果你想让compareTo在等于必须返回false时变为0,那么你必须实现一个Comparator而不是实现Comparable。

public class EventComparator implements Comparator<Event>, Serializable {

    private static final long serialVersionUID = 1L;

    @Override
    public int compare(Event o1, Event o2) {
        return (o1.getTimestamp() < o2.getTimestamp() ? -1
                : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1));
    }

}