views:

120

answers:

3

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
+3  A: 

Firstly, if you are using a TreeSet, the actual behavior of your hashCode 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 and compareTo methods, and the code that instantiates the TreeSet.

However, if I was to guess, it would be that you have overloaded the equals method by declaring it with the signature boolean 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.

@Override
public boolean equals(Object other) { ...

(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 and equals are incorrect. It is still not entirely clear exactly where the bug is because the semantics of both methods depends on the compareTo and equals methods of the Boeking class.

The first if statement of the Ticket.compareTo looks highly suspicious. It looks like the return 1; could cause t1.compareTo(t2) and t2.compareTo(t1) to both return 1 for some tickets t1 and t2 ... and that would definitely be wrong.

Stephen C
^That's what I thought too, but I just added the 3 results that might be of any relevance.
Jasper
+1 probably the best guess so far
NullUserException
@Jasper - those results are all explained by my theory. In particular, the third line of output would be `true` because you are actually calling the overload at that point. But we **really** need to see the actual code!!
Stephen C
This would definitely explain it...
Stefan Kendall
I assume that his equals method takes a type other than "Object".
Stefan Kendall
I have added the Ticket class and it's hashCode(), equals(), compareTo() methods. I hope this helps.Sorry for the language in the code, I'm Belgian.edit: I just noticed Boeking.equals takes type "Boeking" instead of Object like Ticket.equals() does, could this be the issue?
Jasper
So I edited all my equals() methods, all have the @ override annotation and take type Object. Still no good though.. compareTo method seems to be consistent too (double checked Paul Tomblin's tips).
Jasper
+4  A: 

This could happen if your compareTo method isn't consistent. I.e. if a.compareTo(b) > 0, then b.compareTo(a) must be < 0. And if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) must be > 0. If those aren't true, TreeSet can get all confused.

Paul Tomblin
+6  A: 

On compareTo contract

The problem is in your compareTo. Here's an excerpt from the documentation:

Implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.

Your original code is reproduced here for reference:

// 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;
}

Why is the return 1; a bug? Consider the following scenario:

  • Given Ticket t1, t2
  • Given t1.boeking.compareTo(t2.boeking) == 0
  • Given t1.equals(t2) return false
  • Now we have both of the following:
    • t1.compareTo(t2) returns 1
    • t2.compareTo(t1) returns 1

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:

// original declaration; uses raw type!
public class Ticket implements Comparable

it'd be much more appropriate to instead declare something like this:

// improved declaration! uses parameterized Comparable<T>
public class Ticket implements Comparable<Ticket>

Now we can write our compareTo(Ticket) (no longer compareTo(Object)). There are many ways to rewrite this, but here's a rather simplistic one that works:

@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;
   }
}

Now we can also define equals(Object) in terms of compareTo(Ticket) instead of the other way around:

@Override public boolean equals(Object o) {
   return (o instanceof Ticket) && (this.compareTo((Ticket) o) == 0);
}

Note the structure of the compareTo: it has multiple return 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

polygenelubricants
Thank's a lot, following this explanation solved my issue. And thanks to everyone else for pushing me in the right direction as well.I guess in the future I need to be really careful rewriting compareTo() and equals(). Defining equals() in terms of compareTo() isn't something I had thought of, now both methods are consistent.
Jasper
@Jasper, if that fixed the problem, don't just say "Thanks a lot", accept the answer. You might want to throw some upvotes towards those of us who got the answer but didn't give as much detail, too.
Paul Tomblin
^ allright, I wasn't aware there was an accept button to click. And I already tried to upvote the other answers too, yet I can't since I'm new here and haven't got the required reputation yet.
Jasper