Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method reference does not always seem to capture instance

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.

like image 358
Bentaye Avatar asked Jun 05 '19 10:06

Bentaye


People also ask

What is an example of a reference to an instance method?

The following is an example of a reference to an instance method of a particular object: The method reference myComparisonProvider::compareByName invokes the method compareByName that is part of the object myComparisonProvider. The JRE infers the method type arguments, which in this case are (Person, Person).

What is an method reference?

Method references are a special type of lambda expressions. They're often used to create simple lambda expressions by referencing existing methods. There are four kinds of method references: Instance methods of an arbitrary object of a particular type

How to avoid object reference not set to an instance?

If you expect the reference sometimes to be null, you can check for it being null before accessing instance members. 2. Explicitly check for null and provide a default value. In order to avoid object reference not set to an instance of an object, you can choose to check for the null and provide a default value. 3.

Which method references a static method of a particular object?

The method references Person::compareByAge and MethodReferencesExamples::appendStrings are references to a static method. The following is an example of a reference to an instance method of a particular object: The method reference myComparisonProvider::compareByName invokes the method compareByName that is part of the object myComparisonProvider.


2 Answers

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.

like image 74
Eran Avatar answered Oct 29 '22 12:10

Eran


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.

like image 45
Eugene Avatar answered Oct 29 '22 14:10

Eugene