8

Can you please explain why the first lambda is capturing and the second not.

   Runnable createLambdaWithCapture() {
      return System.out::println;
   }    
   Runnable createLambdaWithApparentCapture() {
        return () -> System.out.println();
   }
Savior
  • 3,225
  • 4
  • 24
  • 48
Steven
  • 109
  • 1
  • 3
  • Thank you for the answers. I thought that a method reference is "synthetic sugar" for a lambda. Even intellij idea suggests to replace this method reference with the same non capturing lambda. – Steven Jan 29 '19 at 22:40
  • 1
    See also [What is the equivalent lambda expression for System.out::println](https://stackoverflow.com/q/28023364/2711488) – Holger Jan 30 '19 at 11:51
  • @Holger, nice explanation. Wonder why they dont dig into details in almost every book on lambdas. they just say you can replace a lamdba with a method reference, and don't say there can be some pitfalls. – Steven Jan 30 '19 at 12:16
  • 1
    Well, the main purpose of these books is to simplify, as otherwise, anyone would just read the Java Language Specification instead. But it’s a hard decision, where to draw the line. Unfortunately, there are lots of documents, especially web pages, which seem to be sloppy because their author just doesn’t know it better. You know, everyone can set up a web page… – Holger Jan 30 '19 at 12:26

2 Answers2

8

The first snippet is capturing because it evaluates System.out when the return statement is executed and captures the corresponding object reference to be used within its Runnable#run implementation.

The second snippet generates a Runnable instance that is not capturing because System.out is only evaluated once that instance's run method is invoked. It could have changed since the time the return statement was executed.

Savior
  • 3,225
  • 4
  • 24
  • 48
3

In the first method, System.out is evaluated immediately in the return statement.

The equivalent lambda would be as if you factored System.out out to a variable which then becomes an effectively final closure:

Runnable createLambdaWithCapture() {
    PrintWriter foo = System.out;
    return () -> foo.println(); // foo is captured and effectively final
}

In the second method, System.out (which is a static field) is not final and could be changed later at runtime. It is not invoked until Runnable::run is invoked.

System.out = aPrintStream;
Runnable runnable1 = createLambdaWithCapture();
Runnable runnable2 = createLambdaWithApparentCapture();
System.out = anotherPrintStream;
runnable1.run(); // prints to aPrintStream
runnable2.run(); // prints to anotherPrintStream
Novaterata
  • 4,356
  • 3
  • 29
  • 51
  • 1
    The equivalent lambda would be `PrintWriter foo = Objects.requireNonNull(System.out); return () -> foo.println();` – Holger Jan 30 '19 at 11:49
  • @Holger that might be technically true, and relevant to the other question you answered, but it doesn't seem significant to this question and just becomes noise. – Novaterata Jan 30 '19 at 20:45
  • Speaking of irrelevant but technically true things, [`System.out` *is* `final`](https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#out), though it can still change. But you have to call [`System.setOut(…)`](https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#setOut-java.io.PrintStream-) to change it. This oddity is one of Java’s historical legacies. – Holger Jan 31 '19 at 07:36