Treeset.contains() 问题
Posted
技术标签:
【中文标题】Treeset.contains() 问题【英文标题】:Treeset.contains() problem 【发布时间】:2011-03-26 20:32:25 【问题描述】:所以我一直在为一个问题苦苦挣扎,我想我不妨在这里寻求帮助。
我将 Ticket 对象添加到 TreeSet,Ticket 实现了 Comparable 并覆盖了 equals()、hashCode() 和 CompareTo() 方法。我需要使用 contains() 检查对象是否已经在 TreeSet 中。现在,在向集合中添加 2 个元素后,一切正常,但在添加第三个元素后,它变得一团糟。
在向 TreeSet 添加第三个元素后运行这段小代码,Ticket temp2 是我要检查的对象 (verkoopLijst)。
Ticket temp2 = new Ticket(boeking, TicketType.STANDAARD, 1,1);
System.out.println(verkoop.getVerkoopLijst().first().hashCode());
System.out.println(temp2.hashCode());
System.out.println(verkoop.getVerkoopLijst().first().equals(temp2));
System.out.println(verkoop.getVerkoopLijst().first().compareTo(temp2));
System.out.println(verkoop.getVerkoopLijst().contains(temp2));
返回这个:
22106622
22106622
true
0
false
现在我的问题是这怎么可能?
编辑:
public class Ticket implements Comparable
private int rijNr, stoelNr;
private TicketType ticketType;
private Boeking boeking;
public Ticket(Boeking boeking, TicketType ticketType, int rijNr, int stoelNr)
//setters
@Override
public int hashCode()
return boeking.getBoekingDatum().hashCode();
@Override
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(Object o)
Ticket t = (Ticket) o;
if(this.boeking.equals(t.getBoeking())
&&
this.rijNr == t.getRijNr() && this.stoelNr == t.getStoelNr()
&&
this.ticketType.equals(t.getTicketType()))
return true;
else return false;
/*I adjusted compareTo this way because I need to make sure there are no duplicate Tickets in my treeset. Treeset seems to call CompareTo() to check for equality before adding an object to the set, instead of equals().
*/
@Override
public int compareTo(Object o)
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
if(this.equals(o))
return output;
else return 1;
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
//Getters & Setters
【问题讨论】:
这绝对不应该发生。.contains()
使用 .equals()
进行比较。
boeking.compareTo... ==0
但if(this.equals(o))
怎么可能是假的?如果发生这种情况,那么您的 equals 和 compareTo 彼此不一致。
我想我明白你的意思了,问题是我尝试仅按 Boeking 对象的日期比较放入列表中的对象。所以我可以根据日期得到一个有序的 TreeSet。看来这确实使 compareTo 和 equals 不一致。我会调查的。
@NullUserException 这不适用于 TreeSet 和 TreeMap,因为它们分别依赖于 Comparable.compareTo()
或 Comparator.compare()
。
【参考方案1】:
在compareTo
合约上
问题在于您的compareTo
。这是the documentation的摘录:
实施者必须确保所有
x
和y
的sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
。
此处转载您的原始代码以供参考:
// original compareTo implementation with bug marked
@Override
public int compareTo(Object o)
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
if(this.equals(o))
return output;
else return 1; // BUG!!!! See explanation below!
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
为什么return 1;
是一个错误?考虑以下场景:
Ticket t1, t2
给定t1.boeking.compareTo(t2.boeking) == 0
给定t1.equals(t2)
返回false
现在我们有以下两个:
t1.compareTo(t2)
返回1
t2.compareTo(t1)
返回1
最后一个后果是违反compareTo
合同。
解决问题
首先,您应该利用Comparable<T>
是可参数化泛型类型这一事实。也就是说,而不是:
// original declaration; uses raw type!
public class Ticket implements Comparable
改为这样声明会更合适:
// improved declaration! uses parameterized Comparable<T>
public class Ticket implements Comparable<Ticket>
现在我们可以写我们的compareTo(Ticket)
(不再是compareTo(Object)
)。有很多方法可以重写它,但这里有一个相当简单的方法:
@Override public int compareTo(Ticket t)
int v;
v = this.boeking.compareTo(t.boeking);
if (v != 0) return v;
v = compareInt(this.rijNr, t.rijNr);
if (v != 0) return v;
v = compareInt(this.stoelNr, t.stoelNr);
if (v != 0) return v;
v = compareInt(this.ticketType, t.ticketType);
if (v != 0) return v;
return 0;
private static int compareInt(int i1, int i2)
if (i1 < i2)
return -1;
else if (i1 > i2)
return +1;
else
return 0;
现在我们也可以用compareTo(Ticket)
来定义equals(Object)
,而不是反过来:
@Override public boolean equals(Object o)
return (o instanceof Ticket) && (this.compareTo((Ticket) o) == 0);
注意compareTo
的结构:它有多个return
语句,但实际上逻辑流是相当可读的。还要注意排序标准的优先级是如何明确的,并且如果您有不同的优先级,则可以轻松地重新排序。
相关问题
What is a raw type and why shouldn't we use it? How to sort an array or ArrayList ASC first by x and then by y? Should a function have only one return statement?【讨论】:
非常感谢,按照这个解释解决了我的问题。也感谢其他所有人将我推向正确的方向。我想将来我需要非常小心地重写 compareTo() 和 equals()。用 compareTo() 定义 equals() 不是我想过的,现在这两种方法是一致的。 @Jasper,如果解决了问题,不要只说“非常感谢”,接受答案。您可能想对我们这些得到答案但也没有提供太多细节的人投赞成票。 ^ 好吧,我不知道有一个接受按钮可以点击。我也已经尝试过投票给其他答案,但我不能,因为我是新来的,还没有获得所需的声誉。【参考方案2】:首先,如果您使用TreeSet
,您的hashCode
方法的实际行为不会影响结果。 TreeSet
不依赖散列。
我们真的需要看更多的代码;例如equals
和 compareTo
方法的实际实现,以及实例化 TreeSet
的代码。
但是,如果我猜的话,那将是您重载了equals
方法,方法是使用签名boolean equals(Ticket other)
声明它。这将导致您所看到的行为。要获得所需的行为,您必须覆盖该方法;例如
@Override
public boolean equals(Object other) ...
(最好在@Override
注解中清楚地表明该方法覆盖了超类中的方法,或者实现了接口中的方法。如果您的方法实际上不是覆盖,那么你会得到一个编译错误......这将是一件好事。)
编辑
根据您添加到问题中的代码,问题不在于重载与覆盖。 (正如我所说,我只是在猜测......)
compareTo
和 equals
很可能不正确。仍然不完全清楚错误的确切位置,因为这两种方法的语义都取决于Boeking
类的compareTo
和equals
方法。
Ticket.compareTo
的第一个 if 语句看起来非常可疑。看起来return 1;
可能导致t1.compareTo(t2)
和t2.compareTo(t1)
对于某些票t1
和t2
都返回1
...这肯定是错误的。
【讨论】:
^我也是这么想的,但我只是添加了可能相关的 3 个结果。 @Jasper - 这些结果都可以用我的理论来解释。特别是,输出的第三行将是true
,因为此时您实际上正在调用重载。但是我们真的需要看实际的代码!!
我假设他的 equals 方法采用“Object”以外的类型。
我添加了 Ticket 类,它是 hashCode()、equals()、compareTo() 方法。我希望这有帮助。对不起,代码中的语言,我是比利时人。编辑:我刚刚注意到 Boeking.equals 采用“Boeking”类型而不是像 Ticket.equals() 这样的 Object,这可能是问题吗?
所以我编辑了我所有的 equals() 方法,都有@ override 注释并采用 Object 类型。不过还是不行.. compareTo 方法似乎也很一致(仔细检查了 Paul Tomblin 的提示)。【参考方案3】:
如果您的 compareTo 方法不一致,可能会发生这种情况。 IE。如果a.compareTo(b) > 0
,那么b.compareTo(a)
必须小于0。如果a.compareTo(b) > 0
和b.compareTo(c) > 0
,那么a.compareTo(c)
必须大于0。如果不是这样,TreeSet 可能会很混乱。
【讨论】:
以上是关于Treeset.contains() 问题的主要内容,如果未能解决你的问题,请参考以下文章