【问题标题】:Method reference does not always seem to capture instance方法引用似乎并不总是捕获实例
【发布时间】:2019-06-14 17:28:37
【问题描述】:

我知道这个主题有很多问题,即使是very recent one,但我仍然无法解决一件事。考虑以下功能界面

@FunctionalInterface
interface PersonInterface {
    String getName();
}

还有这个实现:

class Person implements PersonInterface {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

如果我查看这些线程 12,我希望以下代码输出 "Bob" 而不会抛出 NullPointerException,因为据我所知,当我创建供应商时,它会捕获Person 实例。

Person p = new Person("Bob");
Supplier<String> f = p::getName;
p = null;
System.out.println(f.get());

它正确输出"Bob"

现在我不明白的是为什么下面的代码也不输出"Bob"

Person p = new Person("Bob");
Supplier<String> f = p::getName;
p.setName("Alice");
System.out.println(f.get());

它实际上输出"Alice"

在我看来,在第一个示例中,lambda 在创建 Person 对象时捕获了它的状态,并且在调用它时不会尝试重新评估它,而在第二种情况下,它似乎确实如此不捕获它,而是在调用它时重新评估它。

编辑 在重新阅读了其他线程和 Eran 的回答之后,我用 2 个指向同一个实例的人写了那个位:

Person p1 = new Person("Bob");
Person p2 = p1;
Supplier<String> f1 = p1::getName;
Supplier<String> f2 = p2::getName;
p1 = null;
p2.setName("Alice");
System.out.println(f1.get());
System.out.println(f2.get());

现在我可以看到它们都输出"Alice",即使 p1 为空,因此方法引用捕获了实例本身,而不是我错误假设的状态。

【问题讨论】:

  • 我认为如果您更改了人名(如在您的代码块中)并且没有取回更新后的值,那会很奇怪/容易出错

标签: java java-8 instance functional-interface supplier


【解决方案1】:

在我看来,在第一个示例中,lambda 在创建 Person 对象时捕获了它的状态,并且在调用它时不会尝试重新评估它,而在第二种情况下,它似乎确实如此不捕获它,而是在调用它时重新评估它。

首先,它是一个方法引用,而不是 lambda 表达式。

在这两种情况下,方法引用都会捕获对Person 实例的引用(这不是“Person 对象的状态”)。这意味着如果Person 实例的状态发生了变化,那么函数接口方法的执行结果可能会发生变化。

方法引用不会创建它所捕获的引用的Person 实例的副本。

【讨论】:

  • 我想我现在明白了,我刚刚经历了尤里卡时刻。我用我尝试过的东西在我的问题中添加了一个编辑,这为我清除了它。谢谢!
【解决方案2】:

这与 lambda 或方法引用无关在某种程度上,它只是您正在使用的这些构造的副作用。

为了更简单的推理,您可以将其视为:

static class SupplierHolder {
    private final Person p;
    // constructor/getter
}

static class Person {
    private String name;
    // constructor/getter/setter
}

当您创建:Supplier&lt;String&gt; f = p::getName; 时,您可以将其视为创建一个 SupplierHolder,该 Person 作为输入并具有对其getName 的方法引用。

就像在做:

Person p = new Person("Bob");
SupplierHolder sh = new SupplierHolder(p);
p = null; // this has no effect on the reference that SupplierHolder holds
System.out.println(sh.getPerson().getName()); 

在你的第二个例子中,你有:

Person p = new Person("Bob");
SupplierHolder sh = new SupplierHolder(p); 
p.setName("Alice");

现在p 引用和SupplierHolder 持有的引用“作用”在同一个实例上——它们指向同一个对象。

这在现实中并不完全相同,但我猜想证明了这一点。

【讨论】:

  • 谢谢,我想我现在明白了,我已经接受了 Eran 的回答,因为它让我明白了重点。为你点赞。
猜你喜欢
  • 2017-11-08
  • 2011-11-15
  • 2019-11-19
  • 1970-01-01
  • 1970-01-01
  • 2020-06-17
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多