2

I was under the impression that the following lambda expression

() -> object.method()

was equivalent to the method reference

object::method

This is compounded by IntelliJ which keeps nagging me to replace the former with the latter.

However, I have stumbled across a case where this does not seem to be the case. I have some code where I need to access a lot of properties of various beans which may or may not be null. As I did not want to write a whole lot of

T1 var1 = bean1==null?null:bean1.getPropertyA()
T2 var2 = bean2==null?null:bean2.getPropertyB()
T3 var3 = bean3==null?null:bean3.getPropertyC()

lines, I tried to be clever and wrote the following method:

private <T> T nullSafeGet(final Object object, Supplier<T> call) {
  return object != null ? call.get() : null;
}

I can now write the following:

T1 var1 = nullSafeGet(bean1, () -> bean1.getPropertyA());
T2 var1 = nullSafeGet(bean2, () -> bean2.getPropertyB());
T3 var1 = nullSafeGet(bean3, () -> bean3.getPropertyC());

This works perfectly, if the bean is null, the getter is not called and the variable is set to null.

However, I wanted to make the block above even more readable (and IntelliJ also suggested I do the same), so I changed that to:

T1 var1 = nullSafeGet(bean1, bean1::getPropertyA);
T2 var1 = nullSafeGet(bean2, bean2::getPropertyB);
T3 var1 = nullSafeGet(bean3, bean3::getPropertyC);

This behaves exactly the same, when the beans are non-null. However, if any of those beans are null, the call to nullSafeGet throws a NullPointerException.

As far as I can tell the method reference bean::getProperty is not evaluated lazyly (i.e. only if the supplier in nullSafeGet() is invoked), whereas the lambda expression is only evaluated when needed.

Am I doing something wrong or are the two notations indeed not equal in all respects?

The following code demonstrates the issue:

import org.junit.Test;

import java.util.function.Supplier;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class PersonTest {
  private static final String NAME = "Fester Bettertester";

  @Test
  public void testAccessNonNullReference() {
    Person person = new Person(NAME);

    assertEquals(NAME, nullSafeGet(person, person::getName));
  }

  @Test
  public void testAccessNonNullLambda() {
    Person person = new Person(NAME);

    assertEquals(NAME, nullSafeGet(person, () -> person.getName()));
  }

  @Test
  public void testAccessNullLambda() {
    Person person = null;

    assertNull(nullSafeGet(person, () -> person.getName()));
  }

  @Test
  public void testAccessNullReference() {
    Person person = null;

    assertNull(nullSafeGet(person, person::getName)); // this throws a null pointer exception
  }

  private <T> T nullSafeGet(final Object object, Supplier<T> call) {
    return object != null ? call.get() : null;
  }

  public class Person {
    private final String name;

    Person(final String name) {
      this.name = name;
    }

    String getName() {
      return name;
    }
  }
}

Edit: Sorry for posing a duplicate question, I tried searching for previous answers but apparently used the wrong search terms.

I am just editing the question to add that the solution provided by Seelenvirtuose solves my problem!

Urs Beeli
  • 746
  • 1
  • 13
  • 30
  • 2
    As a side note: A better approach would be to have a method ` T nullSafeGet(O object, Function function) { return object != null ? function.apply(object) : null; }` and call it with `assertNull(nullSafeGet(person, Person::getName));`. – Seelenvirtuose Dec 12 '18 at 12:34

1 Answers1

3

From JLS Sec 15.13.3 (emphasis mine):

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.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243