Home > OS >  IllegalArgumentException: Comparison method violates its general contract
IllegalArgumentException: Comparison method violates its general contract

Time:01-19

I haven't faced this issue ever. I just wanted to understand the cause of this error. As per this answer on SO, the following scenario is a violation:

    A > B
    A == C
    B == C

So I tried writing a simple code to create this scenario and see if it throws this error. Here's what I wrote.

    import java.util.*;

    public class Main {
        public static void main(String[] args) {
          List<A> a = new ArrayList<>();
        
            a.add(new A(0,1)); // this is A
            a.add(new A(0,0)); // this is B
            a.add(new A(0,null)); // And this is C
        
        
            Collections.sort(a, new Comparator<A>(){
                public int compare(A a, A b){
                    if(a.i.equals(b.i) && a.j != null && b.j != null){
                        return a.j.compareTo(b.j);
                    }else{
                        return a.i.compareTo(b.i);
                    }
                }
            });
        
            System.out.println(a);
      }
    }

    class A{
        Integer i;
        Integer j;
        A(Integer i, Integer j){
            this.i = i;
            this.j = j;
        }
    
        public String toString(){
            return "[" i "," j "]";
        }
    }

But this doesn't throw any violation error and runs successfully. Any idea why?

CodePudding user response:

Collections.sort is not required to throw this exception. It may or may not. Essentially, if you attempt to do sorting operations (such as invoking Collections.sort, or passing a broken comparator to the constructor of TreeSet, then adding some elements) with a broken comparator, undefined behaviour is 'allowed' by the JVM.

The exception is simply a kindness, and not one you can reliably get. The spec doesn't explain in which specific circumstances you would.

This should be somewhat obvious: If you have a list with 2 elements in it, and you ask to sort it, it's only going to call your comparator just the one time, and that is by definition insufficient to figure out that your comparator is broken. Java certainly isn't going to do some sort of code analysis prior to accepting the comparator (halting problem and such kinda make that impossible in any case).

But I really wanna see it!

No problem. Just toss a few more in there! Wrap

for (int i = 0; i < 100; i  ) {
 // your 3 add statements here
}

around your add statements and, voila, a funny exception. Funny, in that it is itself broken - the message ends in an exclamation point which violates the exception messaging style guide. If you feel endeavourous, you should file a bug report over at OpenJDK about it:

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.TimSort.mergeLo(TimSort.java:781)
    at java.base/java.util.TimSort.mergeAt(TimSort.java:518)
    at java.base/java.util.TimSort.mergeCollapse(TimSort.java:448)
    at java.base/java.util.TimSort.sort(TimSort.java:245)
    at java.base/java.util.Arrays.sort(Arrays.java:1307)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1721)
    at java.base/java.util.Collections.sort(Collections.java:179)
    at Main.main(Main.java:13)

CodePudding user response:

Java cannot possibly detect every possible violation of the Comparator contract. Doing so would run afoul of the halting problem, and even if you had a guarantee that your comparator terminated, you still would have to test every possible input value of type T.

Specifically, Arrays.sort says this about IllegalArgumentException.

Throws:

...

IllegalArgumentException - (optional) if the comparator is found to violate the Comparator contract

If the comparator is found to violate the contract, it throws the exception. But if your list happens to be sorted without Java noticing a problem, it silently succeeds but may produce incorrect results. So if Java, for instance, compares two values twice and gets a different result each time, that's a clear violation. But if there's some complex mathematical proof that your comparator fails in some corner case, it won't necessarily come up during this particular input to Arrays.sort. That doesn't mean the sorted result is correct, however. In fact, there is no correct sorted result if your ordering isn't actually an ordering. (What does it mean to sort a list of numbers in "potato" order?)

  •  Tags:  
  • java
  • Related