psyskeptic
psyskeptic

Reputation: 304

Inner lambda reference Outer lambda variable

There is a Outer lambda that contains inner lambda whis tries to reference var in Outer lambda, how does it possible? Im try to understand what this expression will look like when its compiled to make this possible.

public static void main(String[] args) {
    ExecutorService service = Executors.newScheduledThreadPool(10);
    DoubleStream.of(3.14159, 2.71828)
            .forEach(c -> service.submit(
                    () -> System.out.println(c)));

I'm guess is will compiled to something like this:

new Consumer<Double>() {
    @Override
    public void accept(Double aDouble) {
        new Runnable() {
            @Override
            public void run() {
                System.out.println(aDouble);
            }
        };
    }
};

Am i right?

Upvotes: 1

Views: 1942

Answers (2)

Holger
Holger

Reputation: 298103

The compiled code is closer to

class MyClass {
    public static void main(String[] args) {
        ExecutorService service = Executors.newScheduledThreadPool(10);
        DoubleStream.of(3.14159, 2.71828)
            .forEach(new DoubleConsumer() {
            @Override public void accept(double value) {
                lambda1(service, value);
            }
        });
    }
    private static void lambda1(ExecutorService service, double c) {
        service.submit(new Runnable() {
            @Override public void run() {
                lambda2(c);
            }
        });
    }
    private static void lambda2(double c) {
        System.out.println(c);
    }
}

not having any nesting anymore. Actually, the compiled code doesn’t contain anonymous inner classes. There will be generate classes at runtime fulfilling the interfaces DoubleConsumer and Runnable though, which will invoke these synthetic methods holding the bodies of your lambda expression.

But this sketch is already sufficient to demonstrate that there is no real technical difference between “outer lambda” and “inner lambda”. The nesting exists on the source code level, allowing you to express your intent concisely.

See also “How will Java lambda functions be compiled?

Upvotes: 3

T.J. Crowder
T.J. Crowder

Reputation: 1073968

The innermost lambda can access c from its enclosing lambda because c is effectively final, because nothing ever assigns to it. From the linked portion of the spec:

  • A method, constructor, lambda, or exception parameter (§8.4.1, §8.8.1, §9.4, §15.27.1, §14.20) is treated, for the purpose of determining whether it is effectively final, as a local variable whose declarator has an initializer.

and then above that

  • A local variable whose declarator has an initializer (§14.4.2) is effectively final if all of the following are true:

    • It is not declared final.

    • It never occurs as the left hand side in an assignment expression (§15.26). (Note that the local variable declarator containing the initializer is not an assignment expression.)

    • It never occurs as the operand of a prefix or postfix increment or decrement operator (§15.14, §15.15).

So even though c isn't explicitly final, it's effectively final.

I'm guess is will compiled to something like this:

...

Am i right?

I think that's about it in effect; the compiler doesn't actually do that, lambdas are their own thing. If we wanted to underscore the fact that c is effectively final, we might add final to its parameter declaration there.

Upvotes: 2

Related Questions