(原文来自基于多个字段对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的
下面的回答。
请注意,这里的优点之一是getter被惰性地评估(例如,只有在相关时才评估
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的ComparisonChain进行排序。
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();
}
});
这样更好,但对于最常见的用例需要一些样板代码:默认情况下,空值应该被赋予更小的值。对于空字段,您必须向Guava提供额外的指令以告知其在此情况下要进行何种操作。如果您想要执行特定操作,则这是一种灵活的机制,但通常您希望使用默认情况(即1、a、b、z、null)。
正如下面的评论中所指出的那样,每次比较都会立即评估所有这些getter。
使用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的ComparisonChain一样,这个库类可以轻松地在多个字段上排序,但是还为null值定义了默认行为(即1、a、b、z、null)。然而,除非您提供自己的比较器,否则您不能指定任何其他内容。
再次注意,在下面的评论中指出,对于每个比较,所有这些getter都会立即评估。
因此,最终只取决于风味和灵活性的需要(Guava的ComparisonChain)与简洁的代码(Apache的CompareToBuilder)之间的平衡。
奖励方法:我发现一个很好的解决方案
on 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))