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));
}
}
}
A variation of Gili’s answer to check if the comparator satisfies the requirements described in the compare method’s javadoc — with a focus on completeness and readability, e.g. by naming the variables the same as in the javadoc. Note that this is O(n^3), only use it when debugging, maybe just on a subset of your elements, in order to be fast enough to finish at all.
public static <T> void verifyComparator(Comparator<T> comparator, Collection<T> elements) {
for (T x : elements) {
for (T y : elements) {
for (T z : elements) {
int x_y = comparator.compare(x, y);
int y_x = comparator.compare(y, x);
int y_z = comparator.compare(y, z);
int x_z = comparator.compare(x, z);
// javadoc: The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x))
if (Math.signum(x_y) == -Math.signum(y_x)) { // ok
} else {
System.err.println("not holding: sgn(compare(x, y)) == -sgn(compare(y, x))" //
+ " | x_y: " + x_y + ", y_x: " + y_x + ", x: " + x + ", y: " + y);
}
// javadoc: The implementor must also ensure that the relation is transitive:
// ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.
if (x_y > 0 && y_z > 0) {
if (x_z > 0) { // ok
} else {
System.err.println("not holding: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0" //
+ " | x_y: " + x_y + ", y_z: " + y_z + ", x_z: " + x_z + ", x: " + x + ", y: " + y + ", z: " + z);
}
}
// javadoc: Finally, the implementor must ensure that:
// compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.
if (x_y == 0) {
if (Math.signum(x_z) == Math.signum(y_z)) { // ok
} else {
System.err.println("not holding: compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z" //
+ " | x_y: " + x_y + ", x_z: " + x_z + ", y_z: " + y_z + ", x: " + x + ", y: " + y + ", z: " + z);
}
}
}
}
}
}
- Difference Between
Comparator
andComparable
Interfaces - Comparison Rules for
Comparator
andComparable
- Java Code Having the
comparison method violates its general contract
Error - Java Solutions Using the
Comparator
andComparable
Interfaces
Today, we will learn about the comparison rules used by the Comparator
and Comparable
interfaces, which will lead to possible causes of the comparison method violates its general contract
error in Java. After that, we will understand two solutions using Comparator
and Comparable
interfaces.
Difference Between Comparator
and Comparable
Interfaces
These are two interfaces in Java core libraries. The Comparator
is a function that compares two different objects and is independent of anything else.
It only looks for its two inputs, continues its process with them, and presents the results.
On the other hand, the Comparable
is an interface that we mix with a data class. For instance, we have a class with some data; the Comparable
interface will be used to compare this class’s first object with the same class’s second object.
It means the Comparable
interface indicates that this
instance can be compared with another instance of the same class. Remember, the Comparable
defines the natural
order for the class.
In other respects, it is also a comparison function just like the Comparator
and has the same rules for the return value, transitive, reflexive, etc.
Comparison Rules for Comparator
and Comparable
The comparison method violates its general contract
means that the Comparator
or Comparable
(based on what we are using) has a bug and violates one of the consistency rules. What are the consistency rules?
Let’s learn them below.
We assume that we have written our Java program for integer-type values. So, our comparison function must adhere to the following rules.
-
Given any two integers
a
andb
, the Trichotomy Law must be satisfied, which means exactly one of the following relations must be true:a
is less thanb
(it returns-ve
value ifa < b
)a
equalsb
(it returns0
ifa == b
)a
is greater thanb
(it returns+ve
value ifa > b
)
-
It must satisfy the Transitivity, which means if
a < b
andb < c
then, for any three numbersa
,b
,c
, it impliesa < c
. -
The third rule is about Antisymmetry, where
a < b
implies~b < a
-
Substitutability is also a comparison rule that says, suppose,
a == b
anda < c
; this impliesb < c
. -
The final comparison rule is Reflexivity, where
a == a
; also~a < a
.
If any of these rules are violated, we get the error saying the comparison method violates its general contract
. Let’s learn it with the help of a code example below.
Java Code Having the comparison method violates its general contract
Error
Example Code:
//import libraries
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import static java.util.stream.Collectors.toCollection;
//Main
public class Main {
public static void main(String[] args) {
//generate random numbers
List<Integer> list = new Random(209).ints(32L)
.boxed()
.collect(toCollection(ArrayList::new));
//sort using lambda expression
list.sort(logging((a,b) -> a-b));
}//end main()
//logging the comparisons
static Comparator<Integer> logging(Comparator<Integer> c) {
return (a, b) -> {
int r = c.compare(a, b);
System.err.printf("%,14d %,14d => %,14dn", a, b, r);
return r;
};
}//end logging()
}//end class
In this code, we are generating a few random numbers by using the ints
instance of the Random
class, which is used to generate the stream of random numbers.
For this code, if we sort as list.sort((a, b) -> a - b);
then, we will not be able to identify what the problem is and where it is happening. That’s why we are sorting it via logging
, which will help us to identify it.
It gives us an error, but it also provides a lot of comparisons of numbers. We will not discuss all, but a few of them are enough to find the error.
OUTPUT:
As we can see, the program violates the consistency rule here, resulting in this error. In the next section, let’s have the solution using Comparator
and Comparable
.
Java Solutions Using the Comparator
and Comparable
Interfaces
Let’s use the Comparator
interface with fewer random numbers.
Example Code:
//import libraries
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import static java.util.stream.Collectors.toCollection;
//Main
public class Main {
public static void main(String[] args) {
//generate random numbers
List<Integer> list = new Random(5).ints(32L)
.boxed()
.collect(toCollection(ArrayList::new));
//sort using lambda expression
list.sort(logging((a,b) -> a-b));
}//end main()
//logging the comparisons
static Comparator<Integer> logging(Comparator<Integer> c) {
return (a, b) -> {
int r = c.compare(a, b);
System.err.printf("%,14d %,14d => %,14dn", a, b, r);
return r;
};
}//end logging()
}//end class
This code is executed successfully. We are not going through all the comparisons, but we will see a few of them to confirm.
Check the following screenshot.
OUTPUT:
Let’s learn about the error-free Java code using the Comparable
interface for making comparisons. To use this interface, we have to make a class containing data.
Let’s do it below.
Example Code (Students.java
class):
public class Student implements Comparable<Student> {
private String firstName;
private String lastName;
public Student(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int compareTo(Student other) {
/*
compare the last names and save the result
in `compareResult` variable
*/
int compareResult = this.lastName.compareTo(other.lastName);
/*
If both last names match, then it would be true means 0. So,
dive into the `if` condition and check if the count of their
first name matches.
if this.count == other.count return 0
if this.count > other.count return 1
if this.count < other.count return -1
*/
if(compareResult == 0){
if(this.firstName.chars().count() == other.firstName.chars().count()) {
compareResult = 0;
return compareResult;
}
else if(this.firstName.chars().count() > other.firstName.chars().count()){
compareResult = 1;
return compareResult;
}
else{
compareResult = -1;
return compareResult;
}
}
else{
return compareResult;
}
}
}
Example Code (Main.java
class):
public class Main {
public static void main(String[] args) {
Student std1 = new Student ("Mehvish", "Ashiq");
Student std2 = new Student ("Mehvish", "Ashiq");
System.out.println("Result of Comparison 1: " + std1.compareTo(std2));
Student std3 = new Student ("Aftab", "Ashiq");
Student std4 = new Student ("Mehvish", "Ashiq");
System.out.println("Result of Comparison 2: " + std3.compareTo(std4));
Student std5 = new Student ("Mehr-un-nissa", "Ashiq");
Student std6 = new Student ("Mehvish", "Ashiq");
System.out.println("Result of Comparison 3: " + std5.compareTo(std6));
}
}
OUTPUT:
Result of Comparison 1: 0
Result of Comparison 2: -1
Result of Comparison 3: 1
In this code, we compare the letter count of firstName
if two objects’ lastName
are the same.
As we can see, the results are consistent. If a==b
, a<b
, and a>b
, it returns 0
, -1
, and 1
, respectively.
Comparison method violates its general contract, error can be very painful to debug. I recently encountered this issue in one of the production servers and it was only sometimes the error was thrown. Here I would discuss some cases that can lead to such an error.
Stacktrace
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:777) at java.util.TimSort.mergeAt(TimSort.java:514) at java.util.TimSort.mergeCollapse(TimSort.java:441) at java.util.TimSort.sort(TimSort.java:245) at java.util.Arrays.sort(Arrays.java:1438) at java.util.Arrays$ArrayList.sort(Arrays.java:3895) at java.util.Collections.sort(Collections.java:175)
Possible Coding Mistakes
Example 1:
In the below example the last statement in compare
, return 0
is never executed and the compare method returns 1 even if the integers are equal.
static void sort(Integer... ints){ List<Integer> list = Arrays.asList(ints); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if(o1 < o2){ return -1; } else if (o1 <= o2){ return 1; } return 0; } }); System.out.println(list); }
Solution :
The solution to the above example is quite simple. Change the <=
to <
Example 2:
In the below example two integers being equal is completely ignored.
static void sort(Integer... ints){ List<Integer> list = Arrays.asList(ints); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if(o1 < o2){ return -1; } else { return 1; } } }); System.out.println(list); }
Solution
The solution to this issue is also simple. Add the else block for the equals also.
Example 3:
We can also have a case when a comparison is being done and something is changed by another thread at the same time. If we have a comparison on size of a list but that list is being updated at the same time by another thread. This case is quite hard to debug as it may fail only in very rare cases. In the below example I am updating the list while running the comparison on the same time so it also throws the contract violation error.
static void sort(){ final List<List<Integer>> list = new ArrayList<>(); for(int i= 0 ; i < 5000;i++){ list.add(new ArrayList<>()); } Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { for(int i= 0 ; i < 5000;i++){ list.get(0).add(2); list.get(21).add(2); list.get(300).add(2); } } }); Collections.sort(list, new Comparator<List<Integer>>() { @Override public int compare(List<Integer> o1, List<Integer> o2) { return Integer.compare(o1.size(), o2.size()); } }); System.out.println(list); }
Solution
The solution to this example is not no simple. You have to make sure that the list size is not changed while the comparison is being done. It anyways doesn’t make sense to do the sorting on size because the size is changing. So even if the comparison went through without any errors, the result would not be correct.
Complete Example
Here I have created a test array that throws the error.
import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; public class Test { public static void main(String[] args) { sort(2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3); sort(3, 2, 3, 2, 1, 31); sort(3, 2, 2, 2, 2, 3, 2, 3, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); sort(1, 1, 1, 1, 1, 2, 1, 1, 1); sort(1,3); } static void sort(Integer... ints) { List<Integer> list = Arrays.asList(ints); Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if (o1 < o2) { return -1; } else { return 1; } } }); System.out.println(list); } }
Output :
In the output we see that the above method doesn’t throw the exception always.So for some cases it does pass
[2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] [1, 2, 2, 3, 3, 31] Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:777) at java.util.TimSort.mergeAt(TimSort.java:514) at java.util.TimSort.mergeCollapse(TimSort.java:441) at java.util.TimSort.sort(TimSort.java:245) at java.util.Arrays.sort(Arrays.java:1438) at java.util.Arrays$ArrayList.sort(Arrays.java:3895) at java.util.Collections.sort(Collections.java:175)
The comparison method violates its general contract mistake usually affects your Java program when the comparators have specific transitivity bugs or inconsistencies. In addition, our experts confirmed the same error due to the JDK version, which can affect several commands if the program is older and the functions are new.
As a result, this guide includes the best debugging methods for the comparison method violates the general contract mistake in your document. Moreover, less experienced developers will learn how to recreate the syntax and pinpoint the exact culprit, an essential skill when debugging this aggravating error, so stick around for more.
Contents
- What Causes the Comparison Method To Violate Its General Contract?
- – Writing Basic Comparators and Commands
- – Sorting Out Multiple Long-running Tasks
- Comparison Method Violates the General Contract: Quick Solution
- – Pinpointing Transitivity Bugs in Your Comparators
- Conclusion
What Causes the Comparison Method To Violate Its General Contract?
The comparison method tends to violate and halt the general contract when developers have a Java document with comparators that include specific transitivity bugs and inconsistencies. In addition, the system may launch an identical error message due to an incorrect JDK version that does not support some commands.
This might discourage less skillful programmers and developers because the solution is only sometimes obvious. Fortunately, the complete exception message is quite descriptive because it pinpoints the command that fails so that users can quickly remove the mistake. However, understanding and implementing the solutions in your program requires attention to detail because a single value or symbol can ruin a function. As a result, our experts wrote several chapters that recreate the invalid code and block the program.
Although it is a less typical cause, our experts confirmed an identical inconsistency when sorting our 2D arrays with specific contests. The contest values have a 2D array of simple integers, but the system needs to render them correctly. As a result, the computer throws an error that blocks your program and halts further code alterations. But first, let us discover the script that recreates this mistake.
– Writing Basic Comparators and Commands
This mistake nearly always affects comparators with specific transitivity bugs, which can annoy professional developers. In addition, the bug can affect documents with simple commands, although confusing their functions and values is rare. So, we will show you an example that creates the comparators and overrides the program. This example also includes several dependencies that appear full-proof, but the program does not recognize their values.
The following example features the comparator:
public int compareTo (Object o) {
if(this == o){
return 0;
}
CollectionItem item = (CollectionItem) o;
Card card1 = CardCache.getInstance().getCard(cardId);
Card card2 = CardCache.getInstance().getCard(item.getCardId());
if (card1.getSet() < card2.getSet()) {
return -1;
} else {
if (card1.getSet() == card2.getSet()) {
if (card1.getRarity() < card2.getRarity()) {
return 1;
} else {
if (card1.getId() == card2.getId()) {
if (cardType > item.getCardType()) {
return 1;
} else {
if (cardType == item.getCardType()) {
return 0;
}
return -1;
}
}
return -1;
}
}
return 1;
}
}
Unfortunately, this is different from where our example ends because we must provide the complete exception. The exception message pinpoints the Java utilities that confuse your system, as shown below:
at java.util.ComparableTimSort.mergeHi (ComparableTimSort.java: 865)
at java.util.ComparableTimSort.mergeAt (ComparableTimSort.java: 473)
at java.util.ComparableTimSort.mergeForceCollapse (ComparableTimSort.java: 382)
at java.util.ComparableTimSort.sort (ComparableTimSort.java: 181)
at java.util.ComparableTimSort.sort (ComparableTimSort.java: 156)
at java.util.Arrays.sort (Arrays.java: 412)
at java.util.Collections.sort (Collections.java: 135)
…
As you can tell, the comparator needs adequate utilities, as confirmed by the exact Java locations. Unfortunately, this error can have other culprits.
– Sorting Out Multiple Long-running Tasks
Unfortunately, Confluence throws an identical bug when developers attempt to sort multiple long-running tasks in a single document. Again, the syntax includes several comparators that need to be clarified for your system, although their values are simple.
The following example launches a mistake:
// Sorting by name is not as potent as sorting by start time (unavailable) but better than something
// else we can think of this iteration. Changing this order later cannot raise any blockers
// in production.
Message o1Name = o1.getName();
if (o1Name == null || o1Name.getKey() == null)
return 1; // push o1 to bottom
Message o2Name = o2.getName();
if (o2Name == null || o2Name.getKey() == null)
return -1; // push o2 to bottom
return o1Name.getKey().compareTo (o2Name.getKey());
});
Although it is not apparent, the return values in this example block the comparators. We suggest reading the following chapter because they include the most sophisticated debugging methods for this error.
Comparison Method Violates the General Contract: Quick Solution
The only and most effective solution of the comparison method violates the general contract error is finding out the transitivity bugs along with their causes in your comparators before running any kinds of commands in your program. For this purpose, you can use verify transitivity commands.
Readers can learn more about this in the next example:
return 1;
This example shows the return command has a value of 1, which is different from the dependencies in the same document.
For instance, the return value in the following order is different:
//…
}
return -1;
The negative value in this example confuses the system because both dependencies must be equal. As a result, our experts provide several examples with exact values, and their functions are neat.
The following example includes several dependencies with equal values:
return 1;
}
if (card1.getSet() < card2.getSet()) {
return -1;
};
if (card1.getRarity() < card2.getRarity()) {
return 1;
}
if (card1.getRarity() > card2.getRarity()) {
return -1;
}
if (card1.getId() > card2.getId()) {
return 1;
}
if (card1.getId() < card2.getId()) {
return -1;
}
return cardType – item.getCardType(); //watch out for overflow!
As you can tell, locating transitivity bugs is critical when fixing the mistake. As a result, our experts would like to present a code that debugs your script by pinpointing the error, so keep reading for more.
– Pinpointing Transitivity Bugs in Your Comparators
This solution includes the complete code that debugs the error. So, the following code locates the bug in the comparators using the verify transitivity commands, as shown below:
{
public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
{
for (T first: elements)
{
for (T second: elements)
{
int result1 = comparator.compare(first, second);
int result2 = comparator.compare(second, first);
if (result1 != -result2)
{
// Uncomment the following line to step through the failed case
//comparator.compare(first, second);
throw new AssertionError(“compare(” + first + “, ” + second + “) == ” + result1 +
” but swapping the parameters returns ” + result2);
}
}
}
for (T first: elements)
{
for (T second: elements)
{
int firstGreaterThanSecond = comparator.compare(first, second);
if (firstGreaterThanSecond <= 0)
continue;
for (T third: elements)
{
int secondGreaterThanThird = comparator.compare(second, third);
if (secondGreaterThanThird <= 0)
continue;
int firstGreaterThanThird = comparator.compare(first, third);
if (firstGreaterThanThird <= 0)
{
// Uncomment the following line to step through the failed case
//comparator.compare(first, third);
throw new AssertionError(“compare(” + first + “, ” + second + “) > 0, ” +
“compare(” + second + “, ” + third + “) > 0, but compare(” + first + “, ” + third + “) == ” +
firstGreaterThanThird);
}
}
}
}
}
private Comparators()
{
}
}
So, developers must invoke the Comparators.verifyTransitivity (myComparator, myCollection) command in front of the code that fails. Let us summarize this guide’s critical information in the final chapter.
Conclusion
The comparison method tends to violate and halt the general contract when developers have a Java document with comparators that include specific transitivity bugs and inconsistencies. Therefore, we wrote this debugging guide based on practical knowledge that contains the following critical points:
- Our experts confirmed an identical inconsistency when sorting our 2D arrays with specific contests
- The complete exception message includes indicators that pinpoint the bugs that affect your Java program
- Recreating the mistake with basic commands helps developers and programmers understand the main culprits
- This guide contains two full-proof solutions that require changing the dependency values
- The final solution pinpoints the transitivity bugs and removes them with a simple command
Nothing will help you better understand how to fix this mistake from your computer besides examples that make the whole process simpler. So gear up, open your Java program, find the incorrect comparators, and implement and write code like a pro.
- Author
- Recent Posts
Your Go-To Resource for Learn & Build: CSS,JavaScript,HTML,PHP,C++ and MYSQL. Meet The Team