我对所有的“有点混淆”如果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的一致性有相同的警告
也许我是从错误的角度解决这个问题,我不知道......
答案 0 :(得分:5)
不同的事件可以具有相同的时间戳
按时间戳
对事件进行排序
后一项要求有些不明确。 Collection的迭代器应该按排序顺序返回实例吗?或者,如果循环中的poll()
,集合是否应按排序顺序返回其以前的内容?
iterator()
按顺序返回元素
PriorityQueue
不是这种情况。您可以使用SortedSet
,但那些要求排序顺序与equals一致,正如您正确指出的那样,您无法实现。据我所知,JDK中没有Collection
将其元素按排序顺序保持排序顺序,以使某些元素相等。但是,您可以使用数组或ArrayList
,并在使用Arrays.sort
或Collection.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.sort
,Arrays.sort
或TreeSet
,两者都不一定。
equals
与TreeSet
取消评论:
Set
是一个SortedSet,并明确声明只依赖compareTo / compare。它明确地说:“即使它的排序与equals不一致,集合的行为也是明确定义的;它只是不遵守Set接口的一般契约。”
如果您引用,请引用所有相关部分。整段写着:
请注意,如果要正确实现
Set
接口,则由集合维护的排序(无论是否提供显式比较器)必须与equals 一致。 [...]这是因为 equals
接口是根据TreeSet
操作定义的,但compareTo
实例使用其compare
执行所有元素比较(或Set
)方法,因此,从集合的角度来看,这种方法认为相等的两个元素是相等的。集合的行为定义良好,即使它的排序与equals不一致;它只是没有遵守TreeSet.add
接口的一般合同。
所以是的,它定义明确,但它没有做出问题所要求的:如果你传递Event
Event
与Event
中的另一个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;
}
}
compareTo
,equals
和hashCode
实施取自您在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));
}
}