"Comparison method violates its general contract!"
Asked Answered
U

15

223

Can someone explain me in simple terms, why does this code throw an exception, "Comparison method violates its general contract!", and how do I fix it?

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}
Unbolt answered 30/11, 2011 at 14:31 Comment(6)
What is the name and class of the Exception? Is it an IllegalArgumentException? If I had to guess I would think that you should be doing s1.getParent().equals(s2) instead of s1.getParent() == s2.Probabilism
And the exception that is thrown as well.Randazzo
I don't know much about Java or about Java comparison APIs, but this comparison method seems dead wrong. Suppose s1 is the parent of s2, and s2 is not the parent of s1. Then compareParents(s1, s2) is 0, but compareParents(s2, s1) is 1. That doesn't make sense. (In addition, it's not transitive, like aix mentioned below.)Microelectronics
This error appears to only be produced by a specific library cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/…Foy
in java, you can use equals (return a boolean) or compareTo (return -1, 0 or +1). Override this functions in your Foo class and after that, you can check s1.getParent().equals(s2) ...Cassatt
Whether or not the exception is thrown depends on the used JRE version. Java6 will allow this, Java 7 and 8 will throw the error.Mason
B
298

Your comparator is not transitive.

Let A be the parent of B, and B be the parent of C. Since A > B and B > C, then it must be the case that A > C. However, if your comparator is invoked on A and C, it would return zero, meaning A == C. This violates the contract and hence throws the exception.

It's rather nice of the library to detect this and let you know, rather than behave erratically.

One way to satisfy the transitivity requirement in compareParents() is to traverse the getParent() chain instead of only looking at the immediate ancestor.

Bhagavadgita answered 30/11, 2011 at 14:36 Comment(8)
Introduced in Java 7's java.util.Arrays.sort #7850039Unspoiled
The fact the library is detecting this is awesome. Someone at sun deserves to throw out a giant You're welcome.Mihrab
Can you generalise this answer a bit to make this question more useful as a reference post?Polynomial
@Qix - as much as I love Sun, this was added in Java 7 under the Oracle bannerMohl
@Mohl Damn! Good catch.Mihrab
Transitivity aside, another way to trigger this Exception is to sort on values that may change during the sort. For example, sorting a File[] created by dir.listFiles(): Arrays.sort(files, (a, b) -> (int) (a.lastModified() - b.lastModified()))Babirusa
@Qix-MONICAWASMISTREATED To clear up the history, the TimSort port to Java was written by Josh Bloch (formerly of Sun) who was at Google at the time, and it was integrated by Sun folks into the OpenJDK 7 development line in July 2009. This was before Oracle entered the picture. Oracle was responsible for delivering the JDK 7 release, though. JDK 7 changesetbug reportUltimatum
@Mohl See previous comment.Ultimatum
A
46

Just because this is what I got when I Googled this error, my problem was that I had

if (value < other.value)
  return -1;
else if (value >= other.value)
  return 1;
else
  return 0;

the value >= other.value should (obviously) actually be value > other.value so that you can actually return 0 with equal objects.

Angus answered 23/7, 2012 at 21:14 Comment(1)
I have to add that if any of your value is a NaN (if value is a double or float), it would fail as well.Histolysis
A
34

The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects. For example, you might want to perform a string compare and force empty strings to sort to the end with:

if ( one.length() == 0 ) {
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

But this overlooks the case where BOTH one and two are empty - and in that case, the wrong value is returned (1 instead of 0 to show a match), and the comparator reports that as a violation. It should have been written as:

if ( one.length() == 0 ) {
    if ( two.length() == 0 ) {
        return 0;               // BOth empty - so indicate
    }
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );
Adhibit answered 27/9, 2013 at 1:23 Comment(0)
U
17

Even if your compareTo is holds transitivity in theory, sometimes subtle bugs mess things up... such as floating point arithmetic error. It happened to me. this was my code:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    else
        return 0;

}   

The transitive property clearly holds, but for some reason I was getting the IllegalArgumentException. And it turns out that due to tiny errors in floating point arithmetic, the round-off errors where causing the transitive property to break where they shouldn't! So I rewrote the code to consider really tiny differences 0, and it worked:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if ((this.tfidf - compareTfidf.tfidf) < .000000001)
        return 0;
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    return 0;
}   
Unstressed answered 28/11, 2015 at 12:11 Comment(2)
This was helpful! My code was logically okay but there was an error because of the precision problem.Barto
another idea is to box them and defer to Double.compareSatterfield
D
7

In our case were were getting this error because we had accidentally flipped the order of comparison of s1 and s2. So watch out for that. It was obviously way more complicated than the following but this is an illustration:

s1 == s2   
    return 0;
s2 > s1 
    return 1;
s1 < s2 
    return -1;
Deign answered 21/5, 2015 at 23:14 Comment(0)
B
7

Editing VM Configuration worked for me.

-Djava.util.Arrays.useLegacyMergeSort=true
Ballon answered 23/7, 2019 at 17:14 Comment(2)
Please double check that my attempt to help you with the formatting did not break anything. I am unsure about the - a the start of the proposed solution. Maybe you intended something like a one-item bullet point list instead.Valued
Also please explain how this helps with the described problem. It currently is practically a code-only answer.Valued
H
5

In my case I was doing something like the following:

if (a.someField == null) {
    return 1;
}

if (b.someField == null) {
    return -1;
}

if (a.someField.equals(b.someField)) {
    return a.someOtherField.compareTo(b.someOtherField);
}

return a.someField.compareTo(b.someField);

What I forgot to check was when both a.someField and b.someField are null.

Hashim answered 31/3, 2017 at 15:6 Comment(0)
C
4

I've seen this happen in a piece of code where the often recurring check for null values was performed:

if(( A==null ) && ( B==null )
  return +1;//WRONG: two null values should return 0!!!
Cowitch answered 26/7, 2013 at 9:36 Comment(0)
S
3

Java does not check consistency in a strict sense, only notifies you if it runs into serious trouble. Also it does not give you much information from the error.

I was puzzled with what's happening in my sorter and made a strict consistencyChecker, maybe this will help you:

/**
 * @param dailyReports
 * @param comparator
 */
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
  final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();

  iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
    /**
     * @param o1
     * @param o2
     */
    @Override
    public void pair(T o1, T o2) {
      final int diff = comparator.compare(o1, o2);
      if (diff < Compare.EQUAL) {
        checkConsistency(objectMapSmallerOnes, o1, o2);
        getListSafely(objectMapSmallerOnes, o2).add(o1);
      } else if (Compare.EQUAL < diff) {
        checkConsistency(objectMapSmallerOnes, o2, o1);
        getListSafely(objectMapSmallerOnes, o1).add(o2);
      } else {
        throw new IllegalStateException("Equals not expected?");
      }
    }
  });
}

/**
 * @param objectMapSmallerOnes
 * @param o1
 * @param o2
 */
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
  final List<T> smallerThan = objectMapSmallerOnes.get(o1);

  if (smallerThan != null) {
    for (final T o : smallerThan) {
      if (o == o2) {
        throw new IllegalStateException(o2 + "  cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
      }
      checkConsistency(objectMapSmallerOnes, o, o2);
    }
  }
}

/**
 * @param keyMapValues 
 * @param key 
 * @param <Key> 
 * @param <Value> 
 * @return List<Value>
 */ 
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
  List<Value> values = keyMapValues.get(key);

  if (values == null) {
    keyMapValues.put(key, values = new LinkedList<Value>());
  }

  return values;
}

/**
 * @author Oku
 *
 * @param <T>
 */
public interface IPairIteratorCallback<T> {
  /**
   * @param o1
   * @param o2
   */
  void pair(T o1, T o2);
}

/**
 * 
 * Iterates through each distinct unordered pair formed by the elements of a given iterator
 *
 * @param it
 * @param callback
 */
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
  List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {

    @Override
    public Iterator<T> iterator() {
      return it;
    }

  });

  for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
    for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
      callback.pair(list.get(outerIndex), list.get(innerIndex));
    }
  }
}
Sension answered 12/8, 2014 at 10:37 Comment(3)
Simply invoke checkConsitency metohod with parameter list and comparator.Sension
Your code does not compile. Classes Compare, Convert (and potentially others) are not defined. Please update the code sniplet with a self-contained example.Leavitt
You should fix the typo in checkConsi(s)tency and remove all redundant @param declarations to make the code more readable.Contemporary
M
3

If compareParents(s1, s2) == -1 then compareParents(s2, s1) == 1 is expected. With your code it's not always true.

Specifically if s1.getParent() == s2 && s2.getParent() == s1. It's just one of the possible problems.

Marrin answered 4/9, 2018 at 23:5 Comment(0)
F
1

In my case, it was an infinite sort. That is, at first the line moved up according to the condition, and then the same line moved down to the same place. I added one more condition at the end that unambiguously established the order of the lines.

Frontogenesis answered 2/1, 2021 at 11:56 Comment(0)
L
0

You can't compare object data like this:s1.getParent() == s2 - this will compare the object references. You should override equals function for Foo class and then compare them like this s1.getParent().equals(s2)

Ladder answered 30/11, 2011 at 14:38 Comment(1)
No, actually I think OP is trying to sort a list of some sort, and wants to actually compare references.Bernadine
L
0

So The main reason this happens is when the compare or compareTo method is not following the basic contract of comparison which is transitivity.

`If A>B And B>C then A has to be greater than A has to greater than C`

Please don't directly jump at using

` -Djava.util.Arrays.useLegacyMergeSort=true `

It will make your sorting logic buggy.

`   
    Foo s1 = new Foo();
    Foo s2 = new Foo();
    Foo s3 = new Foo();

    s1.setParent(s2);
    s2.setParent(s3);
    s3.setParent(s1);

    compareParents(s1, s2); // returns -1
    compareParents(s2, s3); // returns 1
    compareParents(s1, s3); // returns 1
`

In these cases your compareParents method will break the transitive property.

Lexicographer answered 4/6, 2023 at 10:13 Comment(0)
A
0

The short explanation for the error is: A comparator suitable for sorting has to return whether one element is greater, smaller or equal to the second.

Your comparator, however, returns whether one is parent of, child of or totally unrelated to the other. Which is something completely different. Especially 'unrelated' is something completely different than 'equal' and a 4th comparison result (which is not supported by the comparator interface or the sorting algorithms)

Now for a simple 'bubblesort' sorting algorithm, it may lead to a result, as 'unrelated' can be assumed to mean 'equal' in the sense of 'no need to switch the two entries'. However, different runs on the same list with a different starting order may lead to a different sorting result. Because while two elements may be unrelated, a third one may very well be related to one or even both of them and slip between them, or below one or behind the other, depending on the order the comparisons are done. For other sorting algorithms, this may lead to endless recursion (and if this is detected, an exception may be thrown or the program may crash or whatever.)

For sorting by parent/child relation, you'll have to implement your own sorting algorithm.

Adopt answered 5/4, 2024 at 14:7 Comment(0)
B
-1

I faced the same issue and I solved it.

//This this your code

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}

The violation is comparing different things with each other.

//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2

In my code, I wanted to sort addresses by their coordination. In the comparator, I compared between X and Y (by mistake), instead of X and X.

//My code:
    private void sortBasedOnX(){
        //addresses is a list of addresses where each address has X and Y
        addresses.sort((o1, o2) -> {

            String a = o1.getAddress().getX(); 
            String b = o2.getAddress().getY(); //<-- this is supposed to be getX

            return Integer.parseInt(a)-Integer.parseInt(b);
        });
    }
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1
Boardwalk answered 10/1, 2022 at 18:12 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.