There are countless questions and answers on Comparable
and Comparator
but none seem to address this question.
Say I have this method compare
which takes any Comparable
implementation of a and b and returns the comparison -1, 0, or 1.
static <U,T extends Comparable<U>> int compare(T a, U b) {
return a.compareTo(b);
}
Works with any Comparable
all is fine and dandy. Method is adopted by every project across the enterprise.
jshell> compare(1, 2);
$1 ==> -1
jshell> compare("id", "hi");
$2 ==> 1
Then out of the blue the data changes and we start having problems.
jshell> compare(null, "hi");
| Exception java.lang.NullPointerException: Cannot invoke "java.lang.Comparable.compareTo(Object)" because "<parameter1>" is null
| at compare (#1:2)
| at (#2:1)
jshell> compare(1, null);
| Exception java.lang.NullPointerException: Cannot read field "value" because "anotherInteger" is null
| at Integer.compareTo (Integer.java:1473)
| at Integer.compareTo (Integer.java:71)
| at compare (#1:2)
| at (#3:1)
Looking for answers on the other SO questions they will all repeat what the documentation says:
Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.
Now that is simply not acceptable! We have the Comparator class with methods like nullsFirst
and nullsLast
so how do we fix this problem, without changing the method name and signature. We want nulls to be less than non-nulls and when both are null, they should be considered equal. Also the client is very particular and will not be entertaining any if equals null or if (a == null)
comparisons mucking about.
How do we change our popular compare method to still accept any two Comparable
s including nulls and produce the following returns.
jshell> compare(1, 2);
$5 ==> -1
jshell> compare("id", "hi");
$6 ==> 1
jshell> compare(null, "hi");
$7 ==> -1
jshell> compare(1, null);
$8 ==> 1
jshell> compare(null, null);
$9 ==> 0
CodePudding user response:
You need to modify your compare
method. Change this:
static <U,T extends Comparable<U>> int compare(T a, U b) {
return a.compareTo(b);
}
to this:
static <U,T extends Comparable<U>> int compare(T a, U b) {
if (a == null) {
return (b == null) ? 0 : -1;
} else if (b == null) {
return 1;
} else {
return a.compareTo(b);
}
}
Client specifically doesn't want any if equals null or if (a == null) comparisons. Why can't we simply use
Comparator.nullsFirst
as it already does what we want?
Because it doesn't work.
(Correction: actually it would as @AndreyB.Panfilov commented on a now deleted answer:
return Comparator.nullsFirst(Comparator.<T>naturalOrder()).compare(a, b);
But that doesn't invalidate what I am about the say next.)
Sometimes client requirements are just so stupid that you have to push back. You need to educate the client. Seriously. It is in their best interests to try.
But if you really have a client who insists beyond all reason that null
literals are The Spawn of Satan (or something) ... you could implement the null
check using Objects.isNull
(javadoc).
CodePudding user response:
This is the actual solution: to change an existing compare method which takes two Comparables
implementing compareTo
, to also allow for null comparison like Comparator.nullsFirst
does, without changing the name or signature of the method, and without using any if statements. Simple? =)
Current method:
static <U,T extends Comparable<U>> int compare(T a, U b) {
return a.compareTo(b);
}
Becomes:
static <T extends Comparable<T>> int compare(T a, T b) {
return Comparator.nullsFirst(Comparator.<T>naturalOrder()).compare(a, b);
}
Since for T extends Comparable<U>
we have U extends T
which makes U instanceof T
, so we can substitute U
for T
and the method signature remains unchanged.
There was never any need for null comparisons or reinventing a perfectly good wheel. Java can do what you want, most certainly it can, you must just be willing to put in the work.
nJoy