IllegalArgumentException on Collections.sort() method

爷,独闯天下 提交于 2019-12-11 03:36:28

问题


I have comparator for strings, which are converted to date. When I pass this comparator to Collections.sort() method I get java.lang.IllegalArgumentException: Comparison method violates its general contract!.

I have read some articles about this exception, but I don't really understand why this exception appears. Any idea ?

private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");   

    Comparator<String> comparator = new Comparator<String>() {
                    @Override
                    public int compare(String o1, String o2) {
                        if (o1 == null && o2 == null) {
                            return 0;
                        }
                        if (o1 == null) {
                            return 1;
                        }
                        if (o2 == null) {
                            return -1;
                        }
                        try {
                            Date first = sdf.parse(o1);
                            Date second = sdf.parse(o2);
                            return first.compareTo(second);
                        } catch (Exception ignored) {
                            return 0;
                        }
                    }
                };

回答1:


In case an exception is thrown, you return 0. That means whenever any of the arguments cannot be parsed, both are considered equal. Think of this example:

a = "01/01/2015"
b = "01/01/2016"
c = "xxx"

Then you get

comparator.compare(a,c) = 0
comparator.compare(b,c) = 0

but

comparator.compare(a,b) != 0

Solution: Try parsing each of the Strings separately, and on exception use null like this:

private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");

Comparator<String> comparator = new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        Date first;
        try {
            first = sdf.parse(o1);
        } catch (Exception ignored) {
            first = null;
        }
        Date second;
        try {
            second = sdf.parse(o2);
        } catch (Exception ignored) {
            second = null;
        }

        if (first == second) {
            return 0;
        }
        if (first == null) {
            return 1;
        }
        if (second == null) {
            return -1;
        }
        return first.compareTo(second);
    }
};



回答2:


The problem is in your try catch block.

Even if 1 of the dates is not parsable, you are returning 0 (which means the objects are equal).

Now let's take this condition.

str1 = "invalid";
str2 = "10/10/2015"; //Consider this to be valid format.
str3 = "12/10/2015";

Now, let's run over the comparisons,

  1. compare str1 and str2: Returns 0 (which means equal)
  2. compare str1 and str3: Returns 0 (which means equal)

Which means, when you compare str2 and str3, then should be equal. (A=B and A=C means B=C).

But when it compares, it returns a negative number. Hence the exception.




回答3:


Nice. You problem is actually not that hard... Imagine you have three Strings...

Date 1 = correct date string for "Today" Date 2 = correct date string for "Tomorrow" Date 3 = XYZ (an non-correct date String that will throw an exception when parsed)

Date 1 < Date 2, obviously. Date 2 > Date 1, also obvious, works fine

But now the magic trick/problem:

Date 3 == Date 1 - because of your exception handling

AND

Date 3 == Date 2 - also because of that (for both you will return 0).

So there is a Date that is EQUAL to both 1 and 2, but 1 and 2 are NOT equal.

Ask yourself, where would you place Date 3 in your list? It has to be on the same "position" as Date 1 (because it is compared with == 0) AND on the same "position" as Date 2 (again, it compares with == 0 with that). BUT Date 1 and Date 2 are NOT on the same position. This is impossible, thus you are getting your "Comparison method violates its general contract!." exception.




回答4:


Your collection of Strings probably has unparsable dates. This leads to returning 0 in situations where it doesn't make sense.

String a = "badString";
String b = "20/12/2012 12:13";
String c = "20/12/2015 13:14";

b is smaller than c and c is greater than b. b and c are thus not equal. But your function says they are both equal to String a! That makes no sense and it is impossible for Collections.sort to sort correctly.

b needs to be before c, c needs to be after b but a needs to be right next to b AND c.

A better way to handle your comparator would be to filter the List of Strings first, so that you're only comparing valid dates. Your function can then just throw a RuntimeException about still not being able to parse Dates.




回答5:


The sorting algorithm used by java.util.Arrays.sort and (indirectly) by java.util.Collections.sort has been replaced. The new sort implementation may throw an IllegalArgumentException if it detects a Comparable that violates the Comparable contract. The previous implementation silently ignored such a situation. If the previous behavior is desired, you can use the new system property, java.util.Arrays.useLegacyMergeSort, to restore previous mergesort behavior



来源:https://stackoverflow.com/questions/31556639/illegalargumentexception-on-collections-sort-method

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!