I work with some legacy code that goes back decades and has a lot of objects where a field being null means "we don't have that information". As such, it has a lot of methods where it wants to do some math on fields, but only if they are not null. This leads to many ugly if statements that look like a procession of null checks, and happen multiple times per method for various length sets of things that can't be null if the contents are going to be used.
For example
public class Foo
{
public Bar getBar()
{
//returns a Bar object that may or may not be null
}
}
Then, elsewhere
if(foo1 != null && foo1.getBar() != null && foo2 != null && .... etc) {
//do math with foo1.getBar(), foo2.getBar(), etc...
}
Anywho, I made a utility to help make these look a little nicer (and also quiet down SonarQube about too many checks in an if) by making a utility class that looks like this:
public class NullCheckUtil
{
private NullCheckUtil()
{}
public static Boolean anyNull(Object... objects)
{
return Stream.of(objects).anyMatch(Objects::isNull);
}
}
Then realized that if foo1 is null, will get NullPointerException before even going in the anyNull.
Ok, lets make it a stream, so they are not evaluated on declaration:
public static Boolean anyNull(Supplier<Object>.... suppliers)
{
return Stream.of(suppliers).map(Supplier::get).anyMatch(Objects::isNull);
}
Then if check can be
if(!NullCheckUtil.anyNull(foo1, foo1::getBar, foo2, foo2::getBar,.... etc){//do the math}
And my IDE seemed happy with that, no problems. But when I ran from console, I'd get NPE with the error claiming to be on the if line again. Whaaaa?
So I did some digging and at first thought maybe this is the reason: Does Java's ArrayList.stream().anyMatch() guarantee in-order processing?
So I changed the anyNull again to
public static Boolean anyNull(Supplier<Object>... suppliers)
{
for(Supplier<Object> supplier : suppliers)
{
if(supplier == null || supplier.get() == null)
{
return true;
}
}
return false;
}
NOPE, still doesn't fix it (for console).
Experimenting to see what could do it I changed the if to
if(!NullCheckUtil.anyNull(()->foo1, ()->foo1.getBar(), ()->foo2, ()->foo2.getBar(), etc...)
And that works fine in IDE and also in console. Tried both in Java 8.151 and Java 8.221, same behavior.
In other words
try
{
NullCheckUtil.anyNull(()->foo1, ()->foo2, ()->foo1.getBar(), ()->foo2.getBar());
}
catch(NullPointerException npe)
{
//does not happen
}
try
{
NullCheckUtil.anyNull(()->foo1, ()->foo2, foo1::getBar, foo2::getBar);
}
catch(NullPointerException npe)
{
// *DOES* happen. What tha….?
}
So there is some difference between ()->obj.method()
and obj::method
lambdas in that the latter gets interpreted right away on declaration? What is that?