I've seen a number of questions on Stack Overflow of the kind of "How do I sort on fields X AND Y?" The most common answers to those questions consists of "Write a custom Comparator."
But what if you have 3 fields you can sort on? Or 13? or 300? My solution is this utility class, so that rather than writing a billion different Comparators for every possible combination you might want, you write just 3 (or 13, etc.) simple ones and essentially composite them to get the desired behavior.
This method is designed to be used as essentially an expanded version of Collections.sort(List list, Comparator c) so I want to adhere to the same contract as much as possible.
I am also interested in ways to improve the documentation/API and finding possibly troublesome corner cases.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* A utility class for sorting a list using multiple sorting criteria.
*
*/
public class Multisorter {
/**
* Sorts the given List using the given Comparators.
*
* The List is sorted by the Comparators in the order the Comparators are
* given; the elements are sorted according to the first Comparator, then
* all of the elements that are considered "equal" are subsorted recursively
* by the next Comparator and so on.
*
* This method makes use of Collections.sort(List<T>, Comparator<T>)
* internally and obeys the same contract.
*
* @param <T>
* the generic type of the list.
*
* @param list
* the List to sort
* @param comparators
* the comparators to sort with
* @return the sorted List
*/
public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
if (comparators.length <= 0) {
throw new IllegalArgumentException("Must provide at least one Comparator to sort with.");
}
Collections.sort(list, comparators[0]);
if (comparators.length > 1) {
List<List<T>> subCollections = new ArrayList<>();
for (T element : list) {
boolean matchFound = false;
for (List<T> subList : subCollections) {
if (comparators[0].compare(element, subList.get(0)) == 0) {
subList.add(element);
matchFound = true;
}
if (matchFound) {
break;
}
}
if (!matchFound) {
int lastIndex = subCollections.size();
subCollections.add(lastIndex, new ArrayList<>());
subCollections.get(lastIndex).add(element);
}
}
// Sort each of the subcollections recursively without the first Comparator
for (List<T> subList : subCollections) {
sort(subList, Arrays.copyOfRange(comparators, 1, comparators.length));
}
// Aggregate all the subcollections
list.clear();
for (List<T> subList : subCollections) {
list.addAll(subList);
}
}
}
}
However, based on comments by Alnitak and rolfl, I would just use the new thenComparing method in Comparator added in Java 8 that I didn't know about.