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!