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

10

我知道这个问题有很多相关的问题,甚至有一个最近的,但是我仍然无法理解一个问题。考虑下面的函数接口

@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的答案后,我写了那段关于两个指向同一实例的Person的代码:

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());

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

现在我看到他们都输出"Alice",尽管p1为空,所以方法引用捕获了实例本身,而不是我错误地认为的其状态。

如果您更改了人的名称(如您的代码块中所示),但未返回更新后的值,那么这可能会很奇怪/容易出错。 - Satyendra Kumar
2个回答

7
首先,它是一个方法引用而不是lambda表达式。
在这两种情况下,都是通过方法引用捕获了对Person实例的引用(这并不是“Person对象的状态”)。这意味着如果更改Person实例的状态,则执行函数接口方法的结果可能会改变。
方法引用并不会创建其捕获的Person实例的副本。

我现在想我明白了,我刚才有个恍然大悟的时刻。我在我的问题中加了一个编辑,尝试了一些东西,这为我解决了问题。谢谢! - Bentaye

6
这与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持有的引用“作用”于同一个实例——它们指向同一个对象。

现实情况并非完全一样,但这证明了观点,我想是这样的。

谢谢,我现在明白了,我已经接受了Eran的回答,因为它让我理解了重点。我也点赞了你的回答。 - Bentaye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接