Search code examples
javalambdajava-8heap-memory

How Memory assigns to lambda | How does it refered,by non super class reference variable


I was creating the implementation of a functional interface, below is my code:

Consumer<Integer> consumer = new Consumer<Integer>() {
    @Override
    public void accept(Integer t) {
        System.out.println(t);
    }
};

As per the Java Documentation (javadoc)

A variable of a class type T can hold a null reference or a reference to an instance of class T or of any class that is a subclass of T.

In the code above, the anonymous object is created, which is a subclass of Consumer, and can be referred to by reference variable consumer, which is fine.

But I saw Consumer is a FunctionalInterface, so I can also do something like this in Java 8:

Using Lambda

Consumer<Integer> consumer = t -> System.out.println(t);

OR Using Method Reference

Consumer<Integer> consumer = System.out::println;

What I know, no sub classes or anonymous classes are being created in both of the above cases. This is creating couple of confusions to me:

1 : Since the variable consumer is not referring to subclass or Anonymous class of Consumer, so isn't this violating the above mentioned concept which is, variable can only refer child/self or null?

2 : How is memory assigned to lambdas, and how does the JVM handle such at run time?


Solution

  • First of all Jean-Baptiste has shown you why the assignment works in the first place.

    Now the part that I think that you are missing is the fact that the generated class of Consumer in case of Consumer<Integer> consumer = t -> System.out.println(t); is only visible at runtime due to invokedynamic.

    Run your class with a flag :

    java -Djdk.internal.lambda.dumpProxyClasses=/Your/Path
    

    And you will notice that there is a generated class (inside a path of folders from your package name of the class) that contains a .class file sort of like this SOQuestion$$Lambda$1.class.

    If you decompile that you will see that it's actually a class that implements Consumer:

    final class org.eugene.so.SOQuestion$$Lambda$1 
                implements java.util.function.Consumer {
         public void accept(java.lang.Object);
    }
    

    If you look at the generated byte code where your lambda is defined, you will see that the lambda expression is actually de-sugared to a static method (for non-captureting lambdas, it could also be non-static if it is a capturing lambda). The code for it:

    private static void lambda$main$0(java.lang.Integer);
    Code:
       0: getstatic     #3  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: invokevirtual #4  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       7: return
    

    As far as how the VM manages memory for it, for an non-capturing lambda you will get a singleton, for a capturing-lambda you will get a new instance of each call. The absolute must read is here