I know... I know... This post is quite old and already answered alot. But Guavas Ordering
is obsolete and Java 8's Comparator
has built in functionality to solve a lot of custom comparison stuff.
Also I wanted to add my approach in case anybody has a similar need for comparing objects via multiple fields in the object, which can be null.
Setup
Let's work with example data of the question.
We have a list of Address
which contains Coordinate
data, which can be null in some situations.
Custom Comparator
Let's asume we sort the list in a class AddressSorter
and we only want to separate the sorting for concrete objects from those that are null. We can achieve this by using a custom Comparator
that makes basic null checking.
public class AddressSorter {
private static final Comparator<Coordinate> COORDINATE_NULL_COMPARATOR = (c1, c2) -> {
if (c1 != null && c2 == null) {
return 1;
}
if (c1 == null && c2 != null) {
return -1;
}
return 0;
}
public List<Address> sortAddressList(List<Address> addresses) {
return addresses.stream()
.sorted(Comparator.compare(Address::getCoordinate, COORDINATE_NULL_COMPARATOR))
.collect(Collectors.toList());
}
}
In this example we use the built in Comparator.comparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator)
This will construct a list where the Address
es with null
as Coordinate
are at the beginning of the returned list.
This will skip any comparison between any two concrete Coordinate
completely
This may look odd, but there are occasions where it is valid to skip the object comparison. For example any LocalDateTime
(or any other timely object) comparison with additional chaining will result in unexpected behaviour if you need to separate the objects by a LocalDateTime
field that can be null.
Compare Coordinate
with null safety
So if you need the comparison of the Coordinate
objects including null-safety you can use the natural order with a null check like this:
public List<Address> sortAddressList(List<Address> addresses) {
return addresses.stream()
.sorted(Comparator.compare(Address::getCoordinate, Comparator.nullsFirst(Comparator.naturalOrder())
.collect(Collectors.toList());
}
Edit: It is also possible to use nullsLast
if you want the Address
es with Coordinate == null
at the end of your list.
Chaining
With that we can also start to chain sortings base on multiple fields of our Address
es, e.g.:
public List<Address> sortAddressList(List<Address> addresses) {
return addresses.stream()
.sorted(Comparator.compare(Address::getCoordinate, Comparator.nullsFirst(Comparator.naturalOrder())
.thenCompare(Address::getId))
.collect(Collectors.toList());
}
So you will end up with a list where the leading Address
es are the once containing null
as Coordinate
sorted by id
and after that all Address
es with a concrete Coordinate
also sorted by id
.
Comparable and Apache Commons
If you want this behaviour as natural order for Address
you can make Address
implement Comparable
and then work for example with Apache Commons CompareToBuilder
:
@Override
public int compareTo(Address address) {
return new CompareToBuilder()
.append(this.coordinate, address.coordinate, Comparator.nullsFirst(Comparator.naturalOrder())
.append(this.id, address.id)
.toComparison();
This then enables you to use sorted()
in the stream, as it makes use of the compareTo
of Address
:
public List<Address> sortAddressList(List<Address> addresses) {
return addresses.stream()
.sorted()
.collect(Collectors.toList());
}