2013-10-23 34 views
5

我们有用于在整个应用程序中对java对象进行排序的复杂比较器的代码。从历史上看,这些都有效,但自从在Java 7中引入TimSort之后,我们偶尔会得到比较方法违反了其总体合约!错误..取决于对象中保存的是什么数据。通过多个字段对Java bean进行排序的正确方法

这是我们传统的比较器的一个示例(这可能是近十年的老 - 原谅dodgyiness):

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来支持这个吗?

+0

我不是超级熟悉TimSort,但有一点可以帮助你是实现某种枚举为你的领域比较而不是试图做复杂的字符串比较操作。我不确定这是否是解决这个问题的好方法,但这是我的一个想法。 –

+0

这里有太多的字符串比较。你应该真的把字符串转换成相应的数字或者其他的东西,然后比较这些字符串,而不是试图挖掘这些混乱的条件。 –

+0

我不认为这个问题是相关的比较是如何确定的(使用字符串或枚举),我认为这更多的是一个事实,即TimSort有此限制: **实现类还必须确保((比较(x,y)> 0)&&(比较(y,z)> 0))意味着比较(x,z)> 0 ** 这意味着如果X和Y是在相同水平上比较,但Y和Z在不同层次上比较,返回的整数可能会打破这个规则。我需要返回一个表示顺序的值,但也不会破坏X> Y> Z规则。 – Sprooose

回答

3

上述Comparator的主要问题在于它不是传递性的。对于较旧的JDK,它可能似乎“有效”,因为它们不提供对破损比较器的检测,但它在一般情况下无法正常工作,并且在JDK 7之前不会显示错误行为。

其来源非传递性是在groupCode财产的条件比较。 考虑情况时比较订单对象A和B作为通过groupCode因为"FUNCTION".equals(B.getRefRltshpTypeCode())和 对象B和C A <乙由于sortOrder字段省略比较被排序为B <Ç由于sortOrder。但是,由于与groupCode的比较,直接比较时A和C可能被订购为C < A.这打破了Comparator的传递性要求。

要解决这个问题groupCode应当总是考虑到和每为其groupCode跳过由于refRltshpTypeCode值应例如被视为比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()); 
} 
+0

+1用于保存按组代码语义排序。 –

2

从@RomanKonovai的answer是正确的,但增加了一些更多的细节。

想想如何代码这三个对象进行比较,并假设所有非参考:

   A   B   C 
Status   UNATTACHED UNATTACHED UNATTACHED 
RefRltshpType CUSTOM  FUNCTION CUSTOM 
Group   Cat  Ball  Apple 
SortOrder  10   20   30 

通过问题的执行下去,我们可以看到一个< B,和B < C, A.换句话说,A < B < C < AA < A。这显然是不合逻辑的,因为取决于StatusRefRltshpType的值,所以排序顺序由GroupSortOrder确定,并且没有什么可以将这两者联系在一起。实际上,这意味着您的排序顺序是未定义的,因为结果完全取决于输入的顺序,即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; 
} 
+0

感谢您的好解释。 – Sprooose

相关问题