(複数のフィールドに基づいてJavaでオブジェクトのリストをソートする方法から)
この要点の作業コード
Java 8ラムダの使用(2019年4月10日追加)
Java 8はこれをラムダによって適切に解決します(ただし、GuavaとApache Commonsはより高い柔軟性を提供します)。
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
以下の @gaoagongの回答に感謝します。
乱雑で複雑:手で並べ替え
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);
}
});
これには多くのタイピングとメンテナンスが必要で、エラーが発生しやすくなります。
反射的な方法: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();
// 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値は、デフォルトでは小さい値にする必要があります。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のComparisonChainと同様に、このライブラリクラスは複数のフィールドで簡単に並べ替えますが、null値(つまり、1、a、b、z、null)のデフォルトの動作も定義します。ただし、独自のコンパレータを提供しない限り、他に何も指定することはできません。
したがって
最終的には、フレーバーと柔軟性の必要性(GuavaのComparisonChain)と簡潔なコード(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));