我们的代码包含复杂的比较器,这些比较器已用于在整个应用程序中对 Java 对象进行排序。从历史上看,这些都是有效的,但自从在 Java 7 中引入 TimSort 以来,我们偶尔会遇到 Comparison method violates its general contract! 错误.. 取决于对象中保存的数据。
这是我们的一个遗留比较器的示例(可能已有将近十年的历史 - 请原谅它的狡猾):
public int compare(TemplateBean b1, TemplateBean b2) {
// avoid null pointer exceptions
if (b1 == null && b2 == null) return 0;
if (b1 == null) return 1;
if (b2 == null) return -1;
int cmp = 0;
if ("UNATTACHED".equals(b1.getStatusCode()) &&
!"UNATTACHED".equals(b2.getStatusCode())) {
cmp = 1;
}
if (!"UNATTACHED".equals(b1.getStatusCode()) &&
"UNATTACHED".equals(b2.getStatusCode())) {
cmp = -1;
}
if (!"UNATTACHED".equals(b1.getStatusCode()) &&
!"UNATTACHED".equals(b2.getStatusCode()) &&
!"FIELDSIMPLE".equals(b1.getRefRltshpTypeCode()) &&
!"FIELDSIMPLE".equals(b2.getRefRltshpTypeCode()) &&
!"CUSTOM".equals(b1.getRefRltshpTypeCode()) &&
!"CUSTOM".equals(b2.getRefRltshpTypeCode()) &&
!"FUNCTION".equals(b1.getRefRltshpTypeCode()) &&
!"FUNCTION".equals(b2.getRefRltshpTypeCode())) {
String parent1 = b1.getGroupCode() == null ? "" : b1.getGroupCode().toUpperCase();
String parent2 = b2.getGroupCode() == null ? "" : b2.getGroupCode().toUpperCase();
cmp = parent1.compareTo(parent2);
}
if (cmp == 0) {
Integer i1 = b1.getSortOrder() == null ? Const.ZERO : b1.getSortOrder();
Integer i2 = b2.getSortOrder() == null ? Const.ZERO : b2.getSortOrder();
cmp = i1.compareTo(i2);
}
if (cmp == 0) {
String s1 = b1.getShortDescription();
if (s1 == null) s1 = "";
String s2 = b2.getShortDescription();
if (s2 == null) s2 = "";
cmp = s1.compareToIgnoreCase(s2);
}
return cmp; }
因此,我想复制此功能,但要使用可安全用于 TimSort 的比较器。
从代码中你可以看到这个比较有多个层次..
- 它将比较组代码。
- 如果组代码相同,它将比较排序顺序。
- 如果排序顺序相同,它将比较描述。
这意味着它将返回特定级别的比较结果。这可能是两个字符串或两个整数的比较结果。我认为这就是破坏 TimSort 的原因。
我能够使这个比较器解决一般契约问题的唯一方法是散列 bean 的内容并执行字符串比较。其他想法包括编写我们自己的排序函数。当然还有更好的方法吗?
bean 是否应该以另一种方式构造来支持这一点?
最佳答案
上面的Comparator
的主要问题是它不是可传递的。它似乎在旧的 JDK 上“工作”,因为它们没有提供对损坏的比较器的检测,但它在一般情况下无法正常工作,并且直到 JDK 7 才揭示错误行为。
其非传递性的来源是在 groupCode
属性上的条件比较。
考虑以下情况:由于 sortOrder
字段由于 "FUNCTION".equals(B.getRefRltshpTypeCode() 而忽略了
和
由于 groupCode
的比较,比较器将对象 A 和 B 作为 A < B )sortOrder
,对象 B 和 C 被排序为 B < C。但是,由于 groupCode
比较,直接比较时 A 和 C 可能被排序为 C < A。这打破了 Comparator
的传递性要求。
要解决此问题,应始终考虑 groupCode
并且应处理由于 refRltshpTypeCode
值而跳过 groupCode
的每个对象例如,小于现在使用 groupCode
进行比较的任何对象。
比较方法应该看起来像(这只是给你一个想法):
public int compare(TemplateBean b1, TemplateBean b2) {
// avoid null pointer exceptions
if (b1 == null && b2 == null) return 0;
if (b1 == null) return 1;
if (b2 == null) return -1;
int cmp = 0;
if ("UNATTACHED".equals(b1.getStatusCode()) &&
!"UNATTACHED".equals(b2.getStatusCode())) {
cmp = 1;
}
if (!"UNATTACHED".equals(b1.getStatusCode()) &&
"UNATTACHED".equals(b2.getStatusCode())) {
cmp = -1;
}
if (shouldBeComparenByGroupCode(b1) != shouldBeComparedByGroupCode(b2)) {
if (!shouldBeComparenByGroupCode(b1)) {
return -1;
} else {
return 1;
}
}
if (shouldBeComparenByGroupCode(b1) && shouldBeComparenByGroupCode(b2)) {
String parent1 = b1.getGroupCode() == null ? "" : b1.getGroupCode().toUpperCase();
String parent2 = b2.getGroupCode() == null ? "" : b2.getGroupCode().toUpperCase();
cmp = parent1.compareTo(parent2);
}
if (cmp == 0) {
Integer i1 = b1.getSortOrder() == null ? Const.ZERO : b1.getSortOrder();
Integer i2 = b2.getSortOrder() == null ? Const.ZERO : b2.getSortOrder();
cmp = i1.compareTo(i2);
}
if (cmp == 0) {
String s1 = b1.getShortDescription();
if (s1 == null) s1 = "";
String s2 = b2.getShortDescription();
if (s2 == null) s2 = "";
cmp = s1.compareToIgnoreCase(s2);
}
return cmp;
}
在哪里
private static boolean shouldBeComparenByGroupCode(TemplateBean b1) {
return !"UNATTACHED".equals(b1.getStatusCode()) &&
!"FIELDSIMPLE".equals(b1.getRefRltshpTypeCode()) &&
!"CUSTOM".equals(b1.getRefRltshpTypeCode()) &&
!"FUNCTION".equals(b1.getRefRltshpTypeCode());
}
关于java - 按多个字段对 Java bean 进行排序的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19531319/