方法引用似乎并不总是捕获实例

Method reference does not always seem to capture instance

我知道有很多关于这个主题的问题甚至 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;
    }
}

如果我查看这些线程 1 and ,我希望以下代码输出 "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 为空,因此方法引用捕获了实例本身,而不是我错误假设的状态。

It seems to me that in the first example the lambda captured the state of the Person object when it was created and does not try to re-evaluate it when it is called, when in the second case, it seems like it did not capture it, but revaluates it when it is called.

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

在这两种情况下,对 Person 实例的引用都被方法引用捕获(而不是 "the state of the Person object")。这意味着如果 Person 实例的状态发生变化,则执行功能接口的方法的结果可能会改变。

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

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

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

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

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

当您创建:Supplier<String> 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 持有的引用 "act" 在同一个实例上 - 它们指向同一个对象。

现实中并不完全一样,但我猜证明了这一点。