方法参考没有被懒惰地评估?

时间:2018-12-12 12:21:37

标签: java lambda lazy-evaluation

我的印象是以下lambda表达式

() -> object.method()

相当于方法参考

object::method

这与IntelliJ更加复杂,IntelliJ一直让我烦恼,用后者代替前者。

但是,我偶然发现了一个似乎并非如此的情况。我有一些代码需要访问各种bean的很多属性,这些属性可能为null,也可能不为null。因为我不想写很多

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

行,我试图变得聪明,并编写了以下方法:

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

我现在可以编写以下内容:

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

如果Bean为null,则不调用getter并将变量设置为null,这是完美的方法。

但是,我想使上面的代码块更具可读性(并且IntelliJ也建议我这样做),所以我将其更改为:

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

当bean为非null时,其行为完全相同。但是,如果这些bean中的任何一个为null,则对nullSafeGet的调用将引发NullPointerException

据我所知,方法引用bean::getProperty不会被懒惰地求值(即,仅当调用nullSafeGet()中的供应商时),​​而lambda表达式仅在需要时才求值。

我做错什么了吗?或者这两种表示在各个方面确实不相等吗?

以下代码演示了该问题:

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;
    }
  }
}

编辑: 很抱歉提出一个重复的问题,我尝试搜索以前的答案,但显然使用了错误的搜索词。

我正在编辑问题,以补充说Seelenvirtuose提供的解决方案可以解决我的问题!

1 个答案:

答案 0 :(得分:3)

来自JLS Sec 15.13.3(重点是我):

  

首先,如果方法引用表达式以ExpressionName或Primary开头,则将评估此子表达式。 如果子表达式的计算结果为null,则会引发NullPointerException ,并且方法引用表达式会突然完成。如果子表达式突然完成,则由于相同的原因,方法引用表达式也会突然完成。