jos*_*123 -1 java sorting comparator java-8
我正在尝试对一些数字进行排序。我收到“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);
}
}
Run Code Online (Sandbox Code Playgroud)
有趣的是,如果我从列表 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);
}
});
Run Code Online (Sandbox Code Playgroud)
以下是我添加一些日志后执行代码时的输出:
prodClass1: [3, A, 1, a, 1] Comparing with prodClass2: [2, A, 1, b, 3]
prodClass1: [2, A, 1, b, 3] Comparing with prodClass2: [3, A, 1, a]
prodClass1: [2, A, 1, b, 3] Comparing with prodClass2: [3, A, 1]
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.base/java.util.TimSort.mergeHi(TimSort.java:903)
at java.base/java.util.TimSort.mergeAt(TimSort.java:520)
at java.base/java.util.TimSort.mergeForceCollapse(TimSort.java:461)
at java.base/java.util.TimSort.sort(TimSort.java:254)
at java.base/java.util.Arrays.sort(Arrays.java:1515)
at java.base/java.util.ArrayList.sort(ArrayList.java:1750)
at java.base/java.util.Collections.sort(Collections.java:179)
at core.ComparatorTest.main(ComparatorTest.java:30)
Run Code Online (Sandbox Code Playgroud)
如果您违反了合同,则sort可以执行以下两种操作之一,并且规范不保证任何一种行为:
因此,如果您没有遇到该异常,并不意味着您的代码没有问题。不存在异常并不能证明不存在错误。因此,“如果我删除一个元素,这个异常就会消失”是没有意义的。这里的关键问题是1A条目。
您可以仅放大"10.A", "1A.2", "2.A.1".
"10".compareTo("1A")为负数 -"10"被认为是 before "1A"。"1A".compareTo("2")为负数 -"1A"被认为是 before "2"。到目前为止,很明显,对吧?然而..
因此,我们有以下场景:
显然,这是不可能的。
比较方法的一般契约包括:
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 之前 - 与“之后”相同)。你违反了最后一条。第二个片段通过将其视为1Aafter 来修复它10。
您的比较代码违反规则的方式有很多,而且正如您所发现的,违反规则并不能保证出现异常。您的“呃,回到字符串排序”的一般方法是一种糟糕的方法。例如,如果一个比另一个“长”,您不应该只是回退到该方法(相反,较长的方法总是需要从较短的方法中获胜/失败)。