Reputation: 33
I would like to be able to sort a list dynamically by the various, selectable properties of an element that may be passed into a method.
For example, lets say I have a Person
class:
interface Person {
String getName();
int getAge();
LocalDate getBirthDate();
}
If I want to be able to sort by name first, then by age second, I would normally do this:
Collections.sort(unsortedPersons, Comparator.comparing(Person::getFirstName).thenComparing(Person::getAge))
But what if I wanted to select those properties dynamically? How could this properly be achieved? I want to be able to have a contract similar to:
List<Person> sortedPersons = sortPersons(
List.of(person1, person2, person3),
List.of(Person::getName, Person::getAge)
);
where the first parameter is the unsorted list of persons, and the second parameter is a list of functions that will create the above comparator chain dynamically.
The problem I have is that the method references below can't resolve, likely due to the fact that the list of functions can return different types.
public static List<Person> sortPersons(List<Person> unsortedPersons, List<Function<Person, ?>> sortableproperties) {
var comparator = sortableproperties.stream()
.map(Comparator::comparing)
.reduce(Comparator::thenComparing)
.orElse(Comparator.naturalOrder());
return unsortedPersons.stream()
.sorted(comparator)
.collect(Collectors.toList());
}
Is there a more elegant way to achieve this?
Upvotes: 3
Views: 346
Reputation: 8114
The main problem is the method sortPersons parameter List<Function<Person, ?>>
which compiler can't recognise when call Comparator.comparing
. Unfortunately, this problem can't be solved by replacing ?
to Comparable<?>
. So the best we can do is to convert the Function
to Comparator<Person>
first.
It may sounds a bit boring if we only have something like
private static <T extends Comparable<T>> Comparator<Person> convertToComparator(Function<Person, T> properties) {
return Comparator.comparing(properties);
}
But it is quite common we need to switch the sort to ascending/descending, so we can write below.
private static <T extends Comparable<T>> Comparator<Person> asc(Function<Person, T> properties) {
return Comparator.comparing(properties);
}
private static <T extends Comparable<T>> Comparator<Person> desc(Function<Person, T> properties) {
return Comparator.comparing(properties).reversed();
}
Then we can do like this.
List<Person> sortedPersons = sortPersons(
List.of(person1, person2, person3),
List.of(asc(Person::getName), desc(Person::getAge))
);
Upvotes: 1