Java 比较问题 - 比较方法违反了其一般契约

问题描述 投票:0回答:1

我正在尝试对一些数字进行排序。我收到“java.lang.IllegalArgumentException:比较方法违反了其一般契约!”当我执行以下代码时出现异常。

import org.apache.commons.lang3.StringUtils;

public class ComparatorTest {

    public static void main(String[] args) {
        List<String> ll = List.of("1.A", "1.A.1", "10.A", "10.A.1", "10.A.2", "10.A.3", "12.A", "12.A.1", "12.A.2",
                "12.A.4", "12.A.6", "1A.2", "2.A.1", "2.A.1.b", "2.A.1.b.1", "2.A.1.b.2", "2.A.1.b.3", "20.A.1",
                "20.A.1.a", "20.A.1.b", "20.A.1.b.1", "20.A.1.b.2", "3.A.1", "3.A.1.a", "3.A.1.a.1", "3.A.1.a.2",
                "3.A.1.a.3", "3.A.1.a.4", "3.A.1.b", "3.A.10", "6.A.1", "9.A.1");
        
        ArrayList<String> l2 = new ArrayList<>(ll);
        Collections.sort(l2, (obj1, obj2) -> {
            try {
                String[] prodClass1 = obj1.split("\\.");
                String[] prodClass2 = obj2.split("\\.");
                for (int i = 0; (i < prodClass1.length) && (i < prodClass2.length); i++) {
                    if (!prodClass1[i].equals(prodClass2[i])) {
                        if (StringUtils.isNumeric(prodClass1[i]) && StringUtils.isNumeric(prodClass2[i])) {
                            return Integer.valueOf(prodClass1[i]).compareTo(Integer.valueOf(prodClass2[i]));
                        } else {
                            return prodClass1[i].compareToIgnoreCase(prodClass2[i]);
                        }
                    }
                }
                return obj1.compareToIgnoreCase(obj2);
            } catch (Exception e) {
                e.printStackTrace();
                return obj1.compareToIgnoreCase(obj2);
            }
        });
        System.out.println(l2);
    }
    
}

有趣的是,如果我从列表 ll 中删除任何 1 个元素,代码就可以正常工作。

我认为这必须做一些与 equals() 和 Compare() 方法相关的事情。但有人能指出这里出了什么问题吗?

如果我在自定义比较器中添加一个额外条件,它就会起作用。为什么会这样呢?谁能指出问题所在。

修改代码

Collections.sort(l2, (obj1, obj2) -> {
            try {
                String[] prodClass1 = obj1.split("\\.");
                String[] prodClass2 = obj2.split("\\.");
                for (int i = 0; (i < prodClass1.length) && (i < prodClass2.length); i++) {
                    if (!prodClass1[i].equals(prodClass2[i])) {
                        if (StringUtils.isNumeric(prodClass1[i]) && StringUtils.isNumeric(prodClass2[i])) {
                            return Integer.valueOf(prodClass1[i]).compareTo(Integer.valueOf(prodClass2[i]));
                        } else if (StringUtils.isNumeric(prodClass1[i]) || StringUtils.isNumeric(prodClass2[i])) {
                            return StringUtils.isNumeric(prodClass1[i]) ? -1 : 1;
                        } else {
                            return prodClass1[i].compareToIgnoreCase(prodClass2[i]);
                        }
                    }
                }
                return obj1.compareToIgnoreCase(obj2);
            } catch (Exception e) {
                e.printStackTrace();
                return obj1.compareToIgnoreCase(obj2);
            }
        });
java sorting java-8 comparator
1个回答
0
投票

如果您违反了合同,那么

sort
可以执行以下两件事之一,并且规范不保证任何一种行为

  • 排序的输出完全是官样文章。
  • 你得到了那个例外。

因此,如果您没有得到该异常,那么没有意味着您的代码没有问题。不存在异常并不能证明不存在错误。因此,“如果我删除一个元素,这个异常就会消失”是没有意义的。这里的关键问题是 1A 条目。

您只能放大

"10.A", "1A.2", "2.A.1"

  • 根据您的代码,10.A 小于 1A.2,因为第一个组件使用字符串排序,并且
    "10".compareTo("1A")
    为负数 -
    "10"
    被视为位于
    "1A"
    之前。
  • 根据您的代码,1A.2 小于 2.A.1,因为第一个组件使用字符串排序,并且
    "1A".compareTo("2")
    为负 -
    "1A"
    被视为位于
    "2"
    之前。

到目前为止,很明显,对吧?然而..

  • 根据您的代码,10.Anot小于2.A.1,因为第一个组件使用整数排序(因为它们都是数字),并且10是after2.

因此,我们有以下场景:

  • A 在 B 之前,B 在 C 之前......
  • 但是 A 在 C 之后。

这显然是不可能的。

比较方法的一般契约包括:

  • a.equals(b)
    ?然后
    a.compareTo(b) == 0
  • a.compareTo(a) == 0
    .
  • a.compareTo(b)
    需要与
    b.compareTo(a)
    相反。
  • a.compareTo(b)
    b.compareTo(c)
    是同一个方向吗?那么
    a.compareTo(c)
    也必须是那个方向(a 在 b 之前,b 在 c 之前?那么 a 也必须在 c 之前 - 与“之后”相同)。

你违反了最后一条。第二个片段通过将

1A
视为在
10
之后修复了该问题。

© www.soinside.com 2019 - 2024. All rights reserved.