[英]Proper way of sorting Java beans by multiple fields
我们有带有复杂比较器的代码,这些代码用于在整个应用程序中对Java对象进行排序。 从历史上讲,这些方法都是可行的,但是自从Java 7中引入TimSort以来,我们偶尔得到的compare 方法违反了它的一般约定! 错误..取决于对象中保存的数据。
这是我们其中一个比较器的示例(可能已有近十年的历史了-请原谅狡猾):
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安全使用的Comparator。
从代码中您可以看到此比较有多个层次。
这意味着它将返回特定级别的比较结果。 这可能是两个字符串或两个整数的比较结果。 我认为这就是打破TimSort的原因。
使这个Comparator能够解决General Contract问题的唯一方法是散列bean的内容并执行字符串比较。 其他想法包括编写我们自己的排序功能。当然有更好的方法吗?
是否应该以另一种方式构造bean来支持这一点?
上述Comparator
器的主要问题是它不是可传递的。 它似乎可以在较旧的JDK上“工作”,因为它们没有提供对损坏的比较器的检测,但是在一般情况下它无法正常工作,并且直到JDK 7才发现错误行为。
其非传递性的根源在于对groupCode
属性的条件比较。 考虑情况当比较对象的命令A和B为A <B由于sortOrder
字段省略了由比较groupCode
因为"FUNCTION".equals(B.getRefRltshpTypeCode())
和对象B和C被排序为B <C由于sortOrder
。 但是由于groupCode
比较, groupCode
比较时,A和C可能按C <A groupCode
。 这打破了Comparator
传递性要求。
要解决这个问题groupCode
应当总是考虑和用于其中每个对象groupCode
被跳过由于refRltshpTypeCode
值应被视为例如作为比其任何对象小groupCode
现在用于比较。
Compare方法应该类似于(这只是为了给您一个想法):
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());
}
@RomanKonovai的答案是正确的,但是添加了更多详细信息。
考虑一下代码如何比较这三个对象,并假定所有非引用:
A B C
Status UNATTACHED UNATTACHED UNATTACHED
RefRltshpType CUSTOM FUNCTION CUSTOM
Group Cat Ball Apple
SortOrder 10 20 30
通过问题的实现,我们可以看到A <B,B <C和C <A。换句话说, A < B < C < A
或A < A
。 这显然是不合逻辑的,并且会发生,因为取决于Status
和RefRltshpType
的值,排序顺序是由Group
或SortOrder
决定的,并且没有将两者捆绑在一起的方法。 从本质上讲,这意味着您的排序顺序是不确定的,因为结果完全取决于输入的顺序,即sort(sort(List))
可能不会给出与sort(List)
相同的结果。
解决此问题的方法是执行以下操作:
private int objectCompare(String allowed, Comparable v1, Comparable v2) {
if (v1 == v2) return 0;
if (v1 == null) return 1;
if (v2 == null) return -1;
boolean c1 = v1.equals(allowed);
boolean c2 = v2.equals(allowed);
return c1 ? c2 ? 0 : 1 : c2 ? -1 : 0;
}
private int objectCompare(Comparable v1, Comparable v2) {
if (v1 == v2) return 0;
if (v1 == null) return 1;
if (v2 == null) return -1;
return v1.compare(v2);
}
public int compare(TemplateBean b1, TemplateBean b2) {
// avoid null pointer exceptions
if (b1 == b2) return 0;
if (b1 == null) return 1;
if (b2 == null) return -1;
int cmp = objectCompare("UNATTACHED", b1.getStatusCode(), b2.getStatusCode());
if (cmp == 0) {
cmp = objectCompare("FIELDSIMPLE", b1.getRefRltshpTypeCode(), b2.getRefRltshpTypeCode());
if (cmp == 0) {
cmp = objectCompare("CUSTOM", b1.getRefRltshpTypeCode(), b2.getRefRltshpTypeCode());
if (cmp == 0) {
cmp = objectCompare("FUNCTION", b1.getRefRltshpTypeCode(), b2.getRefRltshpTypeCode());
if (cmp == 0) {
cmp = objectCompare(b1.getGroupCode(), b2.getGroupCode());
if (cmp == 0) {
cmp = objectCompare(b1.getSortOrder(), b2.getSortOrder());
if (cmp == 0) {
cmp = objectCompare(b1.getShortDescription(), b2.getShortDescription());
}
}
}
}
}
}
return cmp;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.