Collections.sort() 方法上的 IllegalArgumentException

IllegalArgumentException on Collections.sort() method

我有字符串比较器,它被转换为日期。当我将此比较器传递给 Collections.sort() 方法时,我得到 java.lang.IllegalArgumentException: Comparison method violates its general contract!.

我看过一些关于这个异常的文章,但是我不太明白为什么会出现这个异常。有什么想法吗?

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;
                        }
                    }
                };

不错。你的问题其实没那么难...假设你有三个字符串...

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

但是现在魔法trick/problem:

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

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

所以有一个日期与 1 和 2 都相等,但 1 和 2 不相等。

问问自己,您会将日期 3 放在列表中的什么位置?它必须与日期 1 在相同的 "position" 上(因为它与 == 0 进行比较)并且在与日期 2 相同的 "position" 上(同样,它与 == 0 进行比较)。但是日期 1 和日期 2 不在同一位置。这是不可能的,因此您得到了 "Comparison method violates its general contract!." 异常。

问题出在您的 try catch 块中。

即使其中 1 个日期不可解析,您也会返回 0(这意味着对象相等)。

现在我们来看这个条件。

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

现在,让我们运行进行比较,

  1. 比较str1str2:Returns0(等于)
  2. 比较str1str3:Returns0(等于)

也就是说,当你比较str2str3时,应该是equal。 (A=BA=C 表示 B=C)。

但是当它比较时,它 returns 是一个负数。因此例外。

如果抛出异常,你 return 0。这意味着无论何时任何参数无法解析,两者都被认为是相等的。想想这个例子:

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

然后你得到

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

但是

comparator.compare(a,b) != 0

解决方案:尝试分别解析每个字符串,并在异常情况下使用 null,如下所示:

私有简单日期格式 sdf = 新简单日期格式("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);
    }
};

您的字符串集合可能包含无法解析的日期。这会导致在没有意义的情况下返回 0。

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

b小于c,c大于b。 b 和 c 因此不相等。但是你的函数说它们都等于 String a!这毫无意义,Collections.sort 不可能正确排序。

b 需要在 c 之前,c 需要在 b 之后,但 a 需要紧挨着 b AND c。

处理比较器的更好方法是先过滤字符串列表,以便只比较有效日期。然后,您的函数可以抛出一个关于仍然无法解析日期的 RuntimeException。

java.util.Arrays.sort 和 java.util.Collections.sort(间接)使用的排序算法已被替换。如果新的排序实现检测到违反 Comparable 协定的 Comparable,它可能会抛出 IllegalArgumentException。之前的实现默默忽略了这样的情况。如果需要以前的行为,您可以使用新系统 属性、java.util.Arrays.useLegacyMergeSort 来恢复以前的合并排序行为