10

I know there are many questions on the subject even a very recent one but I still can't work my head around one thing. Consider the following functional interface:

@FunctionalInterface
interface PersonInterface {
    String getName();
}

And this implementation:

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

If I look at these threads 1 and 2, I expect the following code to output "Bob" and not throw a NullPointerException because as far as I understand, when I create my Supplier, it captures the Person instance.

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

And it correctly outputs "Bob"

Now what I do not understand is why the following code does not also output "Bob"?

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

It actually outputs "Alice"

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.

EDIT After re-reading the other threads and with Eran's answer, I wrote that bit with 2 Persons pointing to the same instance:

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

Now I can see that they both output "Alice" even though p1 is null and therefore the method reference captured the instance itself, not its state as I wrongly assumed.

Bentaye
  • 9,403
  • 5
  • 32
  • 45
  • I think it would be weird/error-prone if you changed the name of the person (as in your code block) and didn't get back the updated value – Satyendra Kumar Jun 05 '19 at 11:53

2 Answers2

7

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.

First of all, it's a method reference, not a lambda expression.

In both cases a reference to the Person instance is captured by the method reference (which is not "the state of the Person object"). That means that if the state of the Person instance is mutated, the result of executing the functional interface's method may change.

The method reference does not create a copy of the Person instance whose reference it captures.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • I think I understand now, I just had the Eureka moment. I added an edit in my question with something I tried and that clears it for me. Thanks! – Bentaye Jun 05 '19 at 10:59
6

This has nothing to do with lambdas or method references in a way, it's just side effects of these constructs that you are using.

For a much simpler reasoning you could think about it as:

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

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

When you create: Supplier<String> f = p::getName;, you can think about it as creating a SupplierHolder that takes a Person as input and has a method reference to its getName.

It's like doing:

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

In your second example, you have:

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

Now p reference and the reference that SupplierHolder holds, "act" on the same instance - they point to the same Object.

It's not exactly the same in reality, but proves the point, I guess.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Thanks, I think I get it now, I already accepted Eran's answer because it made me understand the point. Upvoted yours. – Bentaye Jun 05 '19 at 11:03