I'm writing some custom Comparators, and I'd like them to push null items to the bottom of the list, regardless of whether I'm sorting ascending or descending. What's a good strategy or pattern for approaching this?
Offhand:
Any other strategies? I'd like to hear about any experiences with different approaches, and any pitfalls for the various strategies.
I agree with Jon Skeet (it's so easy :). I tried to implement a very simple decorator:
class NullComparators {
static <T> Comparator<T> atEnd(final Comparator<T> comparator) {
return new Comparator<T>() {
public int compare(T o1, T o2) {
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
return comparator.compare(o1, o2);
}
};
}
static <T> Comparator<T> atBeginning(final Comparator<T> comparator) {
return Collections.reverseOrder(atEnd(comparator));
}
}
given a Comparator:
Comparator<String> wrapMe = new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
and some test data:
List<String> strings = Arrays.asList(null, "aaa", null, "bbb", "ccc", null);
you can sort with nulls at end:
Collections.sort(strings, NullComparators.atEnd(wrapMe));
[aaa, bbb, ccc, null, null, null]
or at beginning:
Collections.sort(strings, NullComparators.atBeginning(wrapMe));
[null, null, null, ccc, bbb, aaa]
The last option appeals to me a lot. Comparators are really great to chain together. In particular you may well want to write a ReverseComparator
as well as a NullWrappingComparator
.
EDIT: You don't have to write this yourself. If you look at the Ordering class in the Google Collections Library you'll find this and all kinds of other goodies :)
EDIT: Going into more detail to show what I mean about ReverseComparator
...
One word of warning - in the implementation of a ReverseComparator
, reverse the order of the arguments instead of negating the result, as otherwise Integer.MIN_VALUE
is "reversed" to itself.
So this implementation is wrong (assuming original
is the comparator to reverse):
public int compare(T x, T y)
{
return -original.compare(x, y);
}
but this is right:
public int compare(T x, T y)
{
return original.compare(y, x);
}
The reason is that we always want to reverse the comparison, but if original.compare(x, y)
returns int.MIN_VALUE
, then the bad comparer will also return int.MIN_VALUE
, which is incorrect. This is due to the funny property that int.MIN_VALUE == -int.MIN_VALUE
.
Following up on dfa's answer - what I want is that the nulls sort at the end without affecting the order of the non-nulls. So I want something more along the lines of this:
public class NullComparatorsTest extends TestCase {
Comparator<String> forward = new Comparator<String>() {
public int compare(String a, String b) {
return a.compareTo(b);
}
};
public void testIt() throws Exception {
List<String> strings = Arrays.asList(null, "aaa", null, "bbb", "ccc", null);
Collections.sort(strings, NullComparators.atEnd(forward));
assertEquals("[aaa, bbb, ccc, null, null, null]", strings.toString());
Collections.sort(strings, NullComparators.atBeginning(forward));
assertEquals("[null, null, null, aaa, bbb, ccc]", strings.toString());
}
}
public class NullComparators {
public static <T> Comparator<T> atEnd(final Comparator<T> comparator) {
return new Comparator<T>() {
public int compare(T a, T b) {
if (a == null && b == null)
return 0;
if (a == null)
return 1;
if (b == null)
return -1;
return comparator.compare(a, b);
}
};
}
public static <T> Comparator<T> atBeginning(final Comparator<T> comparator) {
return new Comparator<T>() {
public int compare(T a, T b) {
if (a == null && b == null)
return 0;
if (a == null)
return -1;
if (b == null)
return 1;
return comparator.compare(a, b);
}
};
}
}
Full credit to dfa, though - this is just a minor modification of his work.
In Java 8, you can use the Comparator.nullsLast
and Comparator.nullsFirst
static methods to have more null-friendly comparators. Suppose you have a Fruit
class like the following:
public class Fruit {
private final String name;
private final Integer size;
// Constructor and Getters
}
If you want to sort a bunch of fruits by their size and put the null
s at the end:
List<Fruit> fruits = asList(null, new Fruit("Orange", 25), new Fruit("Kiwi", 5));
You can simply write:
Collections.sort(fruits, Comparator.nullsLast(Comparator.comparingInt(Fruit::getSize)));
And the result would be:
[Fruit{name='Kiwi', size=5}, Fruit{name='Orange', size=25}, null]
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