4

Consider the following compareTo method, implementing the Comparable<T> interface.:

@Override
public int compareTo(MyObject o)
{
    if (o.value.equals(value)
        return 0;
    return 1;
}

Apparantly, the programmer implemented the compareTo as if it was equals(). Obviously a mistake. I would expect this to cause Collections.sort() to crash, but it doesn't. Instead it will just give an arbitrairy result: the sorted result is dependant on the initial ordering.

public class MyObject implements Comparable<MyObject>
{

    public static void main(String[] args)
    {
        List<MyObject> objects =
                Arrays.asList(new MyObject[] {
                        new MyObject(1), new MyObject(2), new MyObject(3)
                });
        Collections.sort(objects);
        System.out.println(objects);

        List<MyObject> objects2 =
                Arrays.asList(new MyObject[] {
                        new MyObject(3), new MyObject(1), new MyObject(2)
                });
        Collections.sort(objects2);
        System.out.println(objects2);
    }

    public int  value;

    public MyObject(int value)
    {
        this.value = value;
    }

    @Override
    public int compareTo(MyObject o)
    {
        if (value == o.value)
            return 0;
        return 1;
    }

    public String toString()
    {
        return "" + value;
    }

}

Result:

[3, 2, 1]
[2, 1, 3]

Can we come up with a use case for this curious implementation of the compareTo, or is it always invalid. And in case of the latter, should it throw an exception, or perhaps not even compile?

wvdz
  • 16,251
  • 4
  • 53
  • 90
  • 2
    This sounds more like a missing unit test. The `compareTo` meets the interface override obligations, viz it returns an int :) – StuartLC Oct 22 '14 at 09:57
  • Like this it probably compares hashcodes so it will only return 0 if it is literally the same object reference for value. Otherwise it will return 1. Returning 1 will say for object1.compareTo(object2) that object 1 is greater so it should be moved up. Basically it just reverses the order of the list like this. – G_V Oct 22 '14 at 10:01
  • @user3427079 No hashcodes are ever involved in sorting. – Marko Topolnik Oct 22 '14 at 10:07
  • http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals%28java.lang.Object%29 not even if Object.equals() without any implementation is used like here seems to be the case? Ah wait I didn't see the primitive in the code. – G_V Oct 22 '14 at 10:13
  • The worst thing about the implementation, of course, is that it won't even compile (missing closing parenthesis). – chiastic-security Oct 22 '14 at 10:15
  • 1
    @chiastic-security: actually, that’s the *best thing* about it, as that’s an error the compiler will tell you about. – Holger Oct 22 '14 at 10:18

4 Answers4

5

There's no reason for it to crash or throw an exception.

You're required to fulfil the contract when you implement the method, but if you don't, it just means that you'll get arbitrary results from anything that relies on it. Nothing is going to go out of its way to check the correctness of your implementation, because that would just slow everything down.

A sorting algorithm's efficiency is defined in terms of the number of comparisons it makes. That means that it's not going to add in extra comparisons just to check that your implementation is consistent, any more than a HashMap is going to call .hashcode() on everything twice just to check it gives the same result both times.

If it happens to spot a problem during the course of sorting, then it might throw an exception; but don't rely on it.

chiastic-security
  • 20,430
  • 4
  • 39
  • 67
  • Okay, I agree that it makes no sense to slow down the sorting just to check if the programmer didn't mess up. However, couldn't this be checked compile time? – wvdz Oct 22 '14 at 14:21
  • @popovitsj Certainly *some* bugs *could* be caught by a compiler. But remember that this isn't part of the core language: it's an interface defined in a library. Catching it at compile time would mean either treating it as an exceptional case in the compiler (messy), or having a formal language by which interfaces can specify contracts (also messy) so that the compiler knows what to check for. Even then, not all bugs could be caught. – chiastic-security Oct 22 '14 at 14:51
1

Violating the contract of Comparable or Comparator does not necessarily result in an exception. The sort method won’t spend additional efforts to detect such a situation. Therefore, it might result in an inconsistent order, an apparently correct result or in an exception being thrown.

The actual behavior depends on the input data and the current implementation. E.g. Java 7 introduced TimSort in it’s sort implementation which is more likely to throw an exception for inconsistent Comparable or Comparator implementations than the implementations of earlier Java releases. This might spot errors that remained undetected when using previous Java versions, however, that’s not a feature to aid debugging in the first place, it’s just a side-effect of more sophisticated optimizations.


Note that it isn’t entirely impossible for a compiler or code audit tool to detect asymmetrical behavior of a compare method for simple cases like in your question. However, as far as I know, there are no compilers performing such a check automatically. If you want to be on the safe side, you should always implement unit tests for classes implementing Comparable or Comparator.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I'd be very surprised if compilers did this kind of check, just because they can't do it in the general case. It's as hard as program equivalence, which is undecidable. – chiastic-security Oct 22 '14 at 10:19
  • @chiastic-security: that depends on how you define the “general case”. In most cases, a comparing function will evaluate some properties of the instances and return constant values based on that or delegate to other comparing methods. It *is* possible to traverse the code’s branches and check whether swapping `this` and `arg0` in case of a `Comparable` or `arg0`and `arg1` for a `Comparator` hasn’t inconsistent results. It’s especially easy in this case as this method might return `+1` but never returns `-1` which can’t be right. After all, an audit doesn’t have to catch everything. – Holger Oct 22 '14 at 10:30
0

According to the documentation, Collections.sort() uses a variant of merge sort, which divides the list into multiple sublists and then repeatedly merge those lists repeatedly; the sorting part is done during the merging of those lists; if your comparison method is arbitrary, what will happen is that this merging will be done in an arbitrary order.

Haroldo_OK
  • 6,612
  • 3
  • 43
  • 80
-1

As a result of this every element is bigger than all the other elements.

Depending on the mathematical group or order you want to represent it may be a valid case and therefore there is no reason to throw any errors. However the example you show does not represent the natural ordering of numbers as you know them by standard.

The presented order is not total.

==EDIT==

Thanks for the comment, indeed it is not allowed by the specification to use the compareTo method to implement non-total or non-antisymmetric orders.

Dennis
  • 646
  • 2
  • 6
  • 19
  • 2
    [From the specification](http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html#compareTo(T)): “The implementor must ensure `sgn(x.compareTo(y)) == -sgn(y.compareTo(x))` for all `x` and `y`.” So, *no*, this may *never* be a valid case. – Holger Oct 22 '14 at 10:11