集合.排序与多个字段

2022-08-31 09:53:09

我有一个包含三个字段(所有字符串类型)的“报告”对象列表-

ReportKey
StudentNumber
School

我有一个排序代码像 -

Collections.sort(reportList, new Comparator<Report>() {

@Override
public int compare(final Report record1, final Report record2) {
      return (record1.getReportKey() + record1.getStudentNumber() + record1.getSchool())                      
        .compareTo(record2.getReportKey() + record2.getStudentNumber() + record2.getSchool());
      }

});

由于某种原因,我没有排序顺序。有人建议在田野之间放一个空格,但为什么呢?

你看到代码有什么问题吗?


答案 1

(最初来自基于多个字段对 Java 中的对象列表进行排序的方法)

此要点中的原始工作代码

使用 Java 8 lambda(2019 年 4 月 10 日添加)

Java 8通过lambda很好地解决了这个问题(尽管Guava和Apache Commons可能仍然提供更大的灵活性):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

感谢@gaoagong在下面的回答

请注意,这里的一个优点是可以懒惰地评估getters(例如。 仅在相关时进行评估)。getSchool()

凌乱而复杂:手动排序

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

这需要大量的打字,维护,并且容易出错。唯一的优点是,只有在相关时才调用 getter

反思方式:使用BeanComparator进行排序

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

显然,这更简洁,但更容易出错,因为您通过使用字符串(没有类型安全,自动重构)丢失了对字段的直接引用。现在,如果重命名字段,编译器甚至不会报告问题。此外,由于此解决方案使用反射,因此排序速度要慢得多。

到达那里:使用Google Guava的CompariChain进行排序

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

这要好得多,但对于最常见的用例,这需要一些样板代码:默认情况下,null值的值应该更小。对于空字段,您必须向Guava提供额外的指令,在这种情况下该怎么做。如果你想做一些特定的事情,这是一个灵活的机制,但通常你需要默认的大小写(即1,a,b,z,null)。

正如下面的评论中所指出的,每次比较都会立即评估这些获取器。

使用Apache Commons CompareToBuilder进行排序

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

与Guava的ComparisChain一样,这个库类很容易在多个字段上排序,但也定义了空值的默认行为(即1,a,b,z,null)。但是,您也无法指定其他任何内容,除非您提供自己的比较器。

同样,正如下面的注释中所指出的,对于每次比较,这些 getter 都会立即进行评估。

因此

最终,它归结为风味和对灵活性的需求(Guava的ComparisChain)与简洁的代码(Apache的CompareToBuilder)。

奖金方式

我找到了一个很好的解决方案,它将多个比较器按优先级顺序组合在CodeReview中:MultiComparator

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

当然,Apache Commons Collections已经为此提供了一个实用程序:

ComparatorUtils.chainedComparator(comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));

答案 2

你看到代码有什么问题吗?

是的。为什么要在比较之前将这三个字段相加?

我可能会做这样的事情:(假设字段按照您希望的顺序排序)

@Override public int compare(final Report record1, final Report record2) {
    int c;
    c = record1.getReportKey().compareTo(record2.getReportKey());
    if (c == 0)
       c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
    if (c == 0)
       c = record1.getSchool().compareTo(record2.getSchool());
    return c;
}

推荐