Treeset.contains() problem

后端 未结 3 1135
甜味超标
甜味超标 2021-02-14 13:57

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

3条回答
  •  一个人的身影
    2021-02-14 14:51

    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 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
    public class Ticket implements Comparable
    

    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

    • 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?

提交回复
热议问题