15

I don't understand a couple of things with lambda.

String s = "Hello World";       
Function<Integer, String> f = s::substring;
s = null;
System.out.println(f.apply(5));

Why is the f.apply method still working if s = null. After all, the String object should be deleted by the GC because there is no pointer that points to the object.

One more thing, why don't I need a return statement here?

Function<Integer, String> f = t -> t + "";
0xCursor
  • 2,242
  • 4
  • 15
  • 33
user5327287
  • 410
  • 4
  • 18
  • 3
    There absolutely is a pointer to the object. `f` becomes an object which contains a pointer to `"Hello world"`. `a::foo` evaluates `a` and stores a reference to its result. – Louis Wasserman Jul 31 '18 at 21:36
  • As a side issue, `s`'s initial referent exists in the string constant pool and thus will never be garbage collected (unless the containing class is) – chrylis -cautiouslyoptimistic- Aug 01 '18 at 05:20
  • 2
    Try to ask only **one** question per question. – user202729 Aug 01 '18 at 07:04
  • 1
    For the first question, also see [Why can method reference use non-final variables?](https://stackoverflow.com/questions/33052917/why-can-method-reference-use-non-final-variables) – user202729 Aug 01 '18 at 07:06
  • @LouisWasserman "Avoid answering questions in comments". Currently this question is on HNQ and the comment has 3 upvotes, that (will definitely) cause false impression on visitors. – user202729 Aug 01 '18 at 07:09

3 Answers3

17

The JLS, Section 15.13.3 describes the runtime evaluation of method references.

The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.

(bold emphasis mine)

Basically the reference to s as it is for the method reference is stored for later execution. Here, the string "Hello World" is saved for later execution of the method reference. Because of this, even if you set s to null after the declaration of the method reference, but before you execute the Function, it will still use the string "Hello World".

Setting something to null does not guarantee that the garbage collector will collect it, and it won't guarantee that it's collected immediately.

Also, here, there still is a reference in the method reference, so it won't get garbage collected at all here.

Finally, lambda expression bodies have 2 forms: an expression and a block of statements with (possibly) a return statement. With

Function<Integer, String> f = t -> t + "";

That is an expression. The block statement equivalent would be:

Function<Integer, String> f = t -> { return t + "";};
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • In my example JLS means that f is envocing the method and saving the result and when I using the get function he returns me the ruslt. You say that becuase of f is a reference to the method so the object ("Hello World") is still stored in the memory, you say one thing and he say other thing or I didn't understand? – user5327287 Jul 31 '18 at 21:57
  • I don't know what example in the JLS you're referring to, but Java does not invoke the method (here, `substring`) when it encounters the method reference. It will invoke it later when the functional method is invoked, here, `apply`. – rgettman Jul 31 '18 at 22:01
  • So you are saying that java will save the object (here String) becuase of the pointer to the method (here substring)? – user5327287 Jul 31 '18 at 22:03
  • Yes. When the method reference is encountered, `s` is evaluated and a separate reference to `"Hello World"` is saved for later execution. – rgettman Jul 31 '18 at 22:07
  • So f indirectly reference to "Hello World"? – user5327287 Jul 31 '18 at 22:15
  • The JLS does not specify that the method reference stores the evaluation of `s`. Only that a reference does get stored because of the method reference. – rgettman Jul 31 '18 at 22:19
  • Can you explain me why this is different from my first example https://pastebin.com/MMSA2Bc9 because this isn't working – user5327287 Jul 31 '18 at 22:56
  • @user5327287 in the pastebin snippet `f` is an [anonymous class](https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html) implementing the `Function` interface. The rules for local variable captures are different for method references and anonymous classes. In anonymous classes all captured local variables must be declared `final` or effectively final (only assigned once). – Johnbot Aug 01 '18 at 07:18
11

Let's convert that method reference to a lambda and see what happens:

String s = "Hello World";
Function<Integer, String> f = i -> s.substring(i); // Doesn't compile!
s = null;
System.out.println(f.apply(5));

The above doesn't compile because s is being changed outside of the lambda, so it is not effectively final. Therefore, we can deduce that using a method reference caches the value of s before it's actually used.

See: Is method reference caching a good idea in Java 8?

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • I don't think that is is correct... if you remove the last `s = null` and decompile the generated classes, you will see that they *both* (lambda and method references) try to cache the value - it is a different story for a lambda because the rules of "anonymous class" apply – Eugene Aug 02 '18 at 09:00
  • I thought it too, I tried to use annonymos class instead of method reference but it didn't work because I was needed to change "s" to final. This inferring is good for thinking but alot of times I deduce things in programing and somtimes I wasn't right. – user5327287 Aug 02 '18 at 18:57
  • @user5327287 exactly. using a lambda is like using an anonymous inner class, but using a method reference is not even close to that. More to read https://stackoverflow.com/a/33053161/1059372, to be honest, this is a duplicate of that – Eugene Aug 04 '18 at 10:57
5

One more thing, why don't I need a return statement here?

When specifying only a single lambda statement, its value is automatically returned from the lambda.

Function<Integer, String> f = s::substring;

Java works with pass-by-value. So, f is a reference copy.

  • I always thought it by reference and not value. – user5327287 Jul 31 '18 at 21:41
  • @user5327287 the proof that it is by reference is that in a method parameter ```void c(String s) {s = "xx"; }``` and you main like ```String s = "bb"; c(s); System.out.println(s);``` the value will still be 'bb' since non references was changed. – Marcos Vasconcelos Jul 31 '18 at 21:49