java - 按多个字段对 Java bean 进行排序的正确方法

标签 java sorting comparator

我们的代码包含复杂的比较器,这些比较器已用于在整个应用程序中对 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 的比较器。

从代码中你可以看到这个比较有多个层次..

  1. 它将比较组代码。
  2. 如果组代码相同,它将比较排序顺序。
  3. 如果排序顺序相同,它将比较描述。

这意味着它将返回特定级别的比较结果。这可能是两个字符串或两个整数的比较结果。我认为这就是破坏 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/

相关文章:

java - 从 ArrayList 中删除元素

java - SSLHandshakeException - PKIX 路径构建失败

javascript - JS用字符串和字符串数字排序数组

python - 在从最大到最小元素开始的大型整数列表中查找元素索引的有效方法

java - 比较方法违反了它的一般契约! Java7比较器

Java版本号排序

java - java中一个类的对象在2个不同的JVM上的序列化

algorithm - 基于数据连续性的排序

java - 困惑如何在另一个类中键入比较器

Java Play - 将对象列表与内部的 Map<String, AnotherObject> 绑定(bind)