So I've been struggling with a problem for a while now, figured I might as well ask for help here.
I'm adding Ticket objects to a TreeSet, Ticket implements Comparable and has overridden equals(), hashCode() and CompareTo() methods. I need to check if an object is already in the TreeSet using contains(). Now after adding 2 elements to the set it all checks out fine, yet after adding a third it gets messed up.
running this little piece of code after adding a third element to the TreeSet, Ticket temp2 is the object I'm checking for(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));
returns this:
22106622
22106622
true
0
false
Now my question would be how this is even possible?
Edit:
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
On
compareTo
contractThe problem is in your
compareTo
. Here's an excerpt from the documentation:Your original code is reproduced here for reference:
Why is the
return 1;
a bug? Consider the following scenario:Ticket t1, t2
t1.boeking.compareTo(t2.boeking) == 0
t1.equals(t2)
returnfalse
t1.compareTo(t2)
returns1
t2.compareTo(t1)
returns1
That last consequence is a violation of the
compareTo
contract.Fixing the problem
First and foremost, you should have taken advantage of the fact that
Comparable<T>
is a parameterizable generic type. That is, instead of:it'd be much more appropriate to instead declare something like this:
Now we can write our
compareTo(Ticket)
(no longercompareTo(Object)
). There are many ways to rewrite this, but here's a rather simplistic one that works:Now we can also define
equals(Object)
in terms ofcompareTo(Ticket)
instead of the other way around:Note the structure of the
compareTo
: it has multiplereturn
statements, but in fact, the flow of logic is quite readable. Note also how the priority of the sorting criteria is explicit, and easily reorderable should you have different priorities in mind.Related questions
Firstly, if you are using a
TreeSet
, the actual behavior of yourhashCode
methods won't affect the results.TreeSet
does not rely on hashing.Really we need to see more code; e.g. the actual implementations of the
equals
andcompareTo
methods, and the code that instantiates theTreeSet
.However, if I was to guess, it would be that you have overloaded the
equals
method by declaring it with the signatureboolean equals(Ticket other)
. That would lead to the behavior that you are seeing. To get the required behavior, you must override the method; e.g.(It is a good idea to put in the
@Override
annotation to make it clear that the method overrides a method in the superclass, or implements a method in an interface. If your method isn't actually an override, then you'll get a compilation error ... which would be a good thing.)EDIT
Based on the code that you have added to the question, the problem is not overload vs override. (As I said, I was only guessing ...)
It is most likely that the
compareTo
andequals
are incorrect. It is still not entirely clear exactly where the bug is because the semantics of both methods depends on thecompareTo
andequals
methods of theBoeking
class.The first if statement of the
Ticket.compareTo
looks highly suspicious. It looks like thereturn 1;
could causet1.compareTo(t2)
andt2.compareTo(t1)
to both return1
for some ticketst1
andt2
... and that would definitely be wrong.This could happen if your compareTo method isn't consistent. I.e. if
a.compareTo(b) > 0
, thenb.compareTo(a)
must be < 0. And ifa.compareTo(b) > 0
andb.compareTo(c) > 0
, thena.compareTo(c)
must be > 0. If those aren't true, TreeSet can get all confused.