(元々、複数のフィールドに基づいて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の回答に感謝します。
ここでの利点の1つは、ゲッターが遅延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);
}
});
これには多くの入力とメンテナンスが必要であり、エラーが発生しやすくなります。唯一の利点は、ゲッターが関連する場合にのみ呼び出されることです。
反射的な方法:BeanComparatorを使用した並べ替え
ComparatorChain chain = new ComparatorChain(Arrays.asList(
new BeanComparator("size"),
new BeanComparator("nrOfToppings"),
new BeanComparator("name")));
Collections.sort(pizzas, chain);
明らかにこれはより簡潔ですが、代わりに文字列を使用してフィールドへの直接参照を失うため、エラーが発生しやすくなります(タイプセーフなし、自動リファクタリング)。これで、フィールドの名前が変更されても、コンパイラは問題を報告しません。さらに、このソリューションはリフレクションを使用するため、並べ替えははるかに遅くなります。
行き方:GoogleGuavaの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();
}
});
これははるかに優れていますが、最も一般的なユースケースではボイラープレートコードが必要です。デフォルトでは、null値の値を小さくする必要があります。nullフィールドの場合、その場合に何をするかをGuavaに追加のディレクティブを提供する必要があります。これは、特定のことを実行したい場合は柔軟なメカニズムですが、多くの場合、デフォルトのケース(1、a、b、z、null)が必要です。
そして、以下のコメントに記載されているように、これらのゲッターはすべて、比較ごとにすぐに評価されます。
Apache CommonsCompareToBuilderを使用した並べ替え
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));
}
}
もちろん、ApacheCommonsCollectionsにはすでにこれに役立つユーティリティがあります。
ComparatorUtils.chainedComparator(comparatorCollection)
Collections.sort(list, ComparatorUtils.chainedComparator(comparators));