13

A Java lambda referencing an element from its enclosing scope holds a reference to its enclosing object. A contrived example, with lambda holding ref to MyClass:

class MyClass {
  final String foo = "foo";
  public Consumer<String> getFn() {
    return bar -> System.out.println(bar + foo);
  }
}

This is problematic if the lifetime of the lambda is long; then we've got a ref to MyClass that is long-lived, when it would have otherwise gone out of scope. Here we can optimize by replacing the lambda with a private static class, so that we're only holding a reference to the String we need rather than to the entire class:

class MyClass {

  private static class PrintConsumer implements Consumer<String> {

    String foo;

    PrintConsumer(String foo) {
      this.foo = foo;
    }

    @Override
    public void accept(String bar) {
      System.out.println(bar + foo);
    }
  }

  final String foo = "foo";

  public Consumer<String> getFn() {
    return new PrintConsumer(foo);
  }
}

Unfortunately this is super verbose and destroys the nice syntax we get from using (effectively final) variables from enclosing scope in lambdas. Is this technically optimal? Is there always a tradeoff here between nice syntax and the possibility of keeping a ref longer than necessary?

10
  • 1
    Use C++ en.cppreference.com/w/cpp/language/lambda#Lambda_capture Commented Mar 23, 2016 at 19:53
  • 2
    I think your assumption is incorrect. The lambda does not hold a reference to the MyClass, only to the string. Commented Mar 23, 2016 at 19:55
  • 4
    @PaulBoddington: That's not correct Commented Mar 23, 2016 at 19:57
  • 1
    I stand corrected. Thank you Lukas and apologies to @Noel Commented Mar 23, 2016 at 19:59
  • 1
    @Ingo: this is referenced implicitly Commented Mar 23, 2016 at 20:06

2 Answers 2

15

Assign your member to a local variable first:

class MyClass {
  final String foo = "foo";
  private Consumer<String> getFn() {
    String localFoo = foo;
    return bar -> System.out.println(bar + localFoo);
  }
}

Now, the lambda only captures local variables inside of getFn(). MyClass.this is no longer captured.

Another option, slightly more verbose, delegate to a helper method:

class MyClass {
  final String foo = "foo";
  private Consumer<String> getFn() {
    return getFn(foo);
  }
  private static Consumer<String> getFn(String localFoo) {
    return bar -> System.out.println(bar + localFoo);
  }
}
Sign up to request clarification or add additional context in comments.

10 Comments

@AR.3: foo is a member reference from within the lambda. It needs to be resolved every time the lambda is invoked. For instance, it could be null if the lambda is invoked from the initialiser prior to foo being initialised. It could be changed again because of some reflection magic.
@srborlongan: I don't see any disadvantages, except, perhaps, readability? Higher order lambdas aren't a very common thing (yet)
@AR.3: When the lambda expression accesses MyClass.foo directly, it does use the compile-time constant rather than actually reading the field. The possibility of the field being not initialized yet or the presence of Reflection magic is never considered in Java. However, it still captures this, maybe to follow a certain rule like that any instance field access mandates the capturing of this, however, I can’t find that part inside the Java Language Specification.
@Lukas Eder: when localFoo is declared final, the field foo will never be read at runtime. Instead, the lambda expression will directly use "foo", regardless of what foo really contains when you invoke getFn() (speaking of the first solution, of course). Even without declaring localFoo as final, it will be directly initialized with "foo", not actually reading foo.
I’m not judging whether it is relevant to this specific question (though it’s worth noting that the behavior might be different for the question as-written compared to a possibly different real-life code). However, AR3 brought this up in the first comment, and there was a comment addressing it needing a correction. I only noticed afterwards that AR.3 opened a new specific question regarding that and added a more complex answer there…
|
1

A combination of Lukas Eder's local-final-variable and helper-method-delegation solutions:

class MyClass {
  final String foo = "foo";
  private Consumer<String> getFn() {
    return apply(
      foo,
      localFoo -> bar -> System.out.println(bar + localFoo)
    );
  }
  private static <IN, OUT> OUT apply(
    final IN in,
    final Function<IN, OUT> function
  ) {
    return function.apply(in);
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.