Ссылка на метод не всегда отражает экземпляр - PullRequest
10 голосов
/ 05 июня 2019

Я знаю, что есть много вопросов по этому вопросу, даже совсем недавний , но я все еще не могу обдумать одну вещь.Рассмотрим следующий функциональный интерфейс :

@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 и 2 , я ожидаю, что следующий код выведет "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"

Мне кажется, что в первом примере лямбда захватила состояние объекта 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());

Теперь я вижу, чтоони оба выводят "Alice", даже если p1 равно нулю, и поэтому ссылка на метод захватывает сам экземпляр, а не его состояние, как я ошибочно предположил.

Ответы [ 2 ]

6 голосов
/ 05 июня 2019

Это не имеет ничего общего с лямбдами или ссылками на методы в смысле , это просто побочные эффекты этих конструкций, которые вы используете.

Для более простых рассуждений вы могли бы думать об этом как:

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, «действуют» на один и тот же экземпляр - они указывают на один и тот же Объект.

Это не совсем то же самое в реальности, но я думаю, это доказывает суть.

6 голосов
/ 05 июня 2019

Мне кажется, что в первом примере лямбда захватила состояние объекта Person при его создании и не пытается переоценить его при вызове, когда во втором случае кажетсякак будто он не захватывает его, но переоценивает его при вызове.

Прежде всего, это ссылка на метод, а не лямбда-выражение.

В обоих случаях ссылка наэкземпляр Person захватывается ссылкой на метод (которая не является «состоянием объекта Person»).Это означает, что если состояние экземпляра Person изменено, результат выполнения метода функционального интерфейса может измениться.

Ссылка на метод не создает копию экземпляра Person, чью ссылку он захватывает.

...