-1

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?

Michael
  • 41,989
  • 11
  • 82
  • 128
Smrt
  • 7
  • 3
  • Uhm... doesn't this `Stream.of(objects).anyMatch(Objects::isNull)` return all the objects that are null? So you only end up with nulls? You might want to use `filter()` and `negate()` on that match. – Tschallacka Jan 17 '20 at 17:13
  • `Stream.of(objects).map(Objects::isNull).reduce(false, Boolean::logicalOr);` should do the trick. – Turing85 Jan 17 '20 at 17:16
  • @Tschallacka Yes exactly, it will return true if any are null, false if none of them are. That's the desired behavior. The problem is if you pass in a null foo, and a foo.getBar(), it will throw an exception on the if before you even get in there. Hence doing it with lambda suppliers instead. – Smrt Jan 17 '20 at 17:17
  • @Turing85 See the part about https://stackoverflow.com/questions/38289399/does-javas-arraylist-stream-anymatch-guarantee-in-order-processing, I went back to a for loop explicitly because intermediate stream operations are not guaranteed to run in order of list contents. And your solution still does not address what I mentioned to Tschallacka either. – Smrt Jan 17 '20 at 17:20
  • "*...intermediate stream operations are not guaranteed to run in order...*" - Almost none stream operations are guaranteed to run in order. Why would you even care? As the first answer to the question you posted suggest: if you are interested in order, you probably have side-effects and should not use `Stream`s. The `null`-argument I do not get. [This example](https://ideone.com/rYB72O) shows that no exception will be thrown, even if elements are `null`. – Turing85 Jan 17 '20 at 17:28
  • "*And my IDE seemed happy with that*" I don't understand this. `anyNull` takes a list of `Supplier`s. You pass `foo1` as the first argument, but that's not a `Supplier`. Therefore the next example where you do `()->foo1` is clearly not equivalent. – Michael Jan 17 '20 at 17:29
  • @Smrt also, keep in mind that a `Supplier` is not guaranteed to return the "same instance" on each `get()`-call. It is perfectly legal for a `Supplier` to return something on the first `get()`-call and return a `null` on the next `get()`-call. – Turing85 Jan 17 '20 at 17:32
  • @Turing Neat link, I didn't know about that website. I made this example https://ideone.com/ohUgTb which unfortunately I'm not sure how to get it to compile since it's not letting me instantiate an object for the static main. But if you look at the code you'll see what I'm trying to do. – Smrt Jan 17 '20 at 17:47
  • @Michael what I mean by IDE seems happy is that when I run from Eclipse (well STS but same difference) I don't get a NPE, but when I run from command line I do. Consistently on both counts. – Smrt Jan 17 '20 at 17:48
  • @Smrt FTFY https://ideone.com/1y1KnI – Michael Jan 17 '20 at 17:48
  • @Turing I'm not sure I follow on a Supplier returning different things on subsequent calls for the same object – Smrt Jan 17 '20 at 17:49
  • @Smrt `Object a = supplier.get(); Object b = supplier.get()`, A and B may conceivably be different – Michael Jan 17 '20 at 17:50
  • What do you expect your IDEOne code to demonstrate? – Michael Jan 17 '20 at 17:50
  • @Michael Thank you for the fix, makes sense. Also, it works with no NPE! Hmm ok, I'll have to give that logic from Turing85 for the hasAnyNulls a try. I don't have my code with me today. Will get back to you. I'd have expected it to bomb on line 23 with an NPE on foo2.getBar() – Smrt Jan 17 '20 at 17:54
  • @Smrt If either, foo1 or foo2 is null, it will NPE, since you evaluate `foo1.getBar` eagerly https://ideone.com/cE6Zsl – Michael Jan 17 '20 at 17:54
  • @Michael i'd expect the IDEOne code i wrote and you fixed to throw a NullPointerException on line 23 when it attempts to invoke foo2.getBar() oh wait i'm dumb. One sec.I fixed my dumb and now it does: https://ideone.com/oxN6MX – Smrt Jan 17 '20 at 17:55
  • @Smrt must the parameter of `anyNull(...)` be `Object...`? Or can it be, for example, `Foo...`? – Turing85 Jan 17 '20 at 17:57
  • YES! If either foo1 or foo2 is null it will NPE. Which is why I wrote a supplier version of the anyNull method (see original question), the idea being that the suppliers will get evaluated inside anyNull, and since one of the suppliers is the Foo objects themselves an always before the getBar, it will early return a true. And when I use ()->foo2.getBar() it's fine in IDE and console. When I use foo2::getBar its fine in IDE but NPEs on console. Let me write an example in IDEOne (this tool is great btw, I love it and thank you for letting me know it exists). – Smrt Jan 17 '20 at 17:57
  • @Smrt again, the `Supplier` is not guaranteed to work since a `Supplier` could return a non-`null` for the first call and a `null` on the second call. – Turing85 Jan 17 '20 at 17:58
  • @Turing85 There is nothing protecting his legacy code from the fact that a getter might return a different value on the 2nd call inside the conditional block. tbh i think that concern is mostly irrelevant. – Michael Jan 17 '20 at 18:05
  • Here we go, adjusted the example https://ideone.com/oxN6MX First System.out runs fine (if second is commented out). Second System.out throws NPE on that line. – Smrt Jan 17 '20 at 18:06
  • @SDJ because passing in foo2.getBar() to a Arrays.asList(objects).contains(null); on a null foo2 will NPE before it even gets to be passed in. – Smrt Jan 17 '20 at 18:08
  • @brandizzi yes! precisely – Smrt Jan 17 '20 at 18:57

2 Answers2

0

If the Objects you want to check are always Foo, then you can rewrite the method to:

public static boolean isAnyFooNullOrReturnsAnyGetBarNull(final Foo... foos) {
    return Stream.of(foos)
        .map(foo -> Objects.IsNull(foo) || Objects.isNull(foo.getBar()))
        .reduce(false, Boolean::logicalOr);
}

Ideone example

Notice that this example uses an immutable class Foo. This property is essential since this guarantees that Foo::getBar() for a certain instance will always return the same Bar. If this property is not given, then the problem is not solvable in a streamified solution.

This is also the reason why the attempt of using a Producer<Bar> will not work: the first call to a producer could return a non-null value, while the second call will return a null value. Some classes behave this way, i.e. they allow only a single consumption of a resource/property.

If the parameter of the method has to be Object..., then the problem is not solvable with Streams.

Turing85
  • 18,217
  • 7
  • 33
  • 58
  • Unfortunately they are not always Foo. There is hundreds of possible object types they could be, some with many getters. I'm just using Foo here for example purposes to demonstrate delta between ()->foo.getBar() and foo::getBar. Again, the issue is not *inside* isAnyNull, it's on the calling code. See https://ideone.com/oxN6MX – Smrt Jan 17 '20 at 18:23
  • I do appreciate the effort, believe me, but I think we're talking past each other :( – Smrt Jan 17 '20 at 18:24
  • No, we are not. What you want is not possible. Either create a proper interface with a proper contract and let all of your classes implement that interface (i.e. enforcing that you can use a `Foo...` and that calls to `getBar()` do not change), or do it the way you do it now. [There is no free lunch](https://en.wikipedia.org/wiki/No_free_lunch_theorem). – Turing85 Jan 17 '20 at 18:24
  • I think I may have found my answer in https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.3 Specifically: "First, if the method reference expression begins with an ExpressionName or a Primary, this subexpression is evaluated. If the subexpression evaluates to null, a NullPointerException is raised, and the method reference expression completes abruptly. If the subexpression completes abruptly, the method reference expression completes abruptly for the same reason. ". It's just odd/funny that the lambda form does not trigger it while the method reference one does. – Smrt Jan 17 '20 at 18:47
  • It is not. Lambdas are lazily evaluated, i.e. they are only evaluated when they are actually called. – Turing85 Jan 17 '20 at 18:53
0

Found older 100% applicable and correct answer after modifying the search terms (i had tried earlier with no luck): java.lang.NullPointerException is thrown using a method-reference but not a lambda expression

It even answers why Eclipse is behaving "nice" (Eclipse bug).

Smrt
  • 7
  • 3