I have a list of objects and I want to sort it alphabetically by an attribute. But I want to add an exception rule if this attribute matches a specific string. For example:
public class Car {
String name;
}
List<Car> cars = asList(
new Car("Unassigned"),
new Car("Nissan"),
new Car("Yamaha"),
new Car("Honda"));
List<Car> sortedCars = cars
.stream
.sorted(Comparator.comparing(Car::getName))
.collect(Collectors.toList());
If cars.name == "Unassigned"
then this car should remain at the end of the list and the result would be:
[Car<Honda>, Car<Nissan>, Car<Yamaha>, Car<Unassigned>]
List<Car> sortedCars = cars
.stream()
.sorted(Comparator.comparing(
Car::getName,
Comparator.comparing((String x) -> x.equals("Unassigned"))
.thenComparing(Comparator.naturalOrder())))
.collect(Collectors.toList());
There are a lot of things going on here. First I am using Comparator.comparing(Function, Comparator)
; then (String x) -> x.equals("Unassigned")
which actually compares a Boolean
(that is Comparable
); then the fact that (String x)
is used - as this type witness is used to correctly infer the types...
The most direct and and easy-to-read solution is probably to write a custom comparator that implements your sorting logic.
You can still use the Comparator.comparing
method to make it a bit prettier, though:
public static final String UNASSIGNED = "Unassigned";
List<Car> cars = List.of(
new Car("Unassigned"),
new Car("Nissan"),
new Car("Yamaha"),
new Car("Honda"));
List<Car> sortedCars = cars.stream()
.sorted(Comparator.comparing(Car::getName, (name1, name2) -> {
if (name1.equals(name2)) return 0;
if (name1.equals(UNASSIGNED)) return 1;
if (name2.equals(UNASSIGNED)) return -1;
return name1.compareTo(name2);
}))
.collect(toList());
It is possible to extract the "at-the-end" functionality to a separate comparable combinator method. Like this:
List<Car> sortedCars = cars.stream()
.sorted(Comparator.comparing(Car::getName, withValueAtEnd(UNASSIGNED)))
.collect(toList());
public static <T extends Comparable<T>> Comparator<T> withValueAtEnd(T atEnd) {
return withValueAtEnd(atEnd, Comparator.naturalOrder());
}
public static <T> Comparator<T> withValueAtEnd(T atEnd, Comparator<T> c) {
return (a, b) -> {
if (a.equals(atEnd)) return 1;
if (b.equals(atEnd)) return -1;
return c.compare(a, b);
};
}
Also, it's good style to use a named constant for special values like your "Unassigned"
.
Also, note that if you don't need to keep the unsorted cars
list, then you can sort that list in place instead of using a stream:
cars.sort(UNASSIGNED_COMPARATOR);
You could just replace "Unassigned" with an end-of-alphabet string in your comparator.
Comparator.comparing(car -> car.getName().equals("Unassigned") ? "ZZZ" : car.getName())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With