Java 8 Lambda in details part V : Functional interface definition and lambda expression implementation

In this last post we’ll look at the functional interface formal definition and the way lambda expressions are implemented.

DISCLAIMER: all the details exposed in this post were observed related to the JDK 8 demo version as of July 10th 2012. Since the JDK is still in beta, some assertions may not hold in future

Please note that all the example code in this post can be found on my GitHub repository https://github.com/doanduyhai/Java8_Lambda_In_Details

 

I Functional interface definition

It seems weird to give a definition of a functional interface (aka SAM interface) in the last post but the truth is that I discover the subtle semantic of functional interface only recently.

Usually people naively think that a functional interface must contain only one single abstract method. The real definition is less restrictive indeed.

An interface is considered a functional interface if it contains one and only one abstract method with no default implementation

No assertion is made about the possibility for this interface to declare static fields or many defenders methods. It means that a functional interface can

  1. have one abstract method and many defenders methods and static fields
  2. have one abstract method and inherit defenders from parent interfaces and static fields

The implication of these subtle details is huge indeed. We can have lambda expressions with default behaviors embedded (via defender methods)!

The code example in the next chapter will prove it.

 

II Lambda expression implementation

Did you ever wonder how the JDK 8 implements an lambda expression ? The following article gives us some insights.

Basically, the compiler will create anonymous inner classes to emulate the functioning of lambda expressions. Two possible scenarios there:

  1. the lambda expression is stateless and does not capture any variable: an anonymous inner class is created with a no argument constructor
  2. the lambda expression does capture variables (this or local variable): they are injected through anonymous inner class constructor as parameters

Let consider the following sample:

public interface SAMWithDefender
{
	String staticvar = "static var in SAM";
	
	void test();
	
	void whoAmI() default 
	{
		System.out.println("I am "+this);
	}
}

public class LambdaInstance
{

	public static SAMWithDefender createLambda()
	{
		return () -> {System.out.println("");};
	}

	public static SAMWithDefender createStatefullLambda(String input)
	{
		return () -> {System.out.println("input = "+input);};
	}

	public static void main(String[] args)
	{
		SAMWithDefender samInstance1 = LambdaInstance.createLambda();
		SAMWithDefender samInstance2 = LambdaInstance.createStatefullLambda("statefull");

		System.out.println("\n");
		samInstance1.whoAmI();
		samInstance2.whoAmI();
		System.out.println("");
	}
}

We define an interface with a static field and 2 methods. One of them is provided a default implementation, the other is let abstract (line 5). This interface meets the requirement to be a functional interface (one single abstract method) as mentioned in the previous chapter.

In the main class, we define 2 lambda expressions, one stateless (line 28) and one capturing local variable (line 29).

I compile the class and use the “javap -verbose” command to examine the generated bytecode.

Definition of fr.doan.lambda.instance.LambdaInstance class:

{
  public fr.doan.lambda.instance.LambdaInstance();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public static fr.doan.lambda.sam.instance.SAMWithDefender createLambda();
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #2                  // class fr/doan/lambda/instance/LambdaInstance$1
         3: dup
         4: invokespecial #3                  // Method fr/doan/lambda/instance/LambdaInstance$1."<init>":()V
         7: areturn
      LineNumberTable:
        line 10: 0

  public static fr.doan.lambda.sam.instance.SAMWithDefender createStatefullLambda(java.lang.String);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #4                  // class fr/doan/lambda/instance/LambdaInstance$2
         3: dup
         4: aload_0
         5: invokespecial #5                  // Method fr/doan/lambda/instance/LambdaInstance$2."<init>":(Ljava/lang/String;)V
         8: areturn
      LineNumberTable:
        line 15: 0 
}

At lines 16 & 18, the static method createLambda() is instanciating a new anonymous inner class “fr/doan/lambda/instance/LambdaInstance$1” with no argument constructor.

At lines 27 & 30, the static method createStatefullLambda() is instanciating a new anonymous inner class “fr/doan/lambda/instance/LambdaInstance$2” with the captured method parameter as constructor argument.

Let’s decompile the anonymous inner class “fr/doan/lambda/instance/LambdaInstance$2

{
class fr.doan.lambda.instance.LambdaInstance$2 implements fr.doan.lambda.sam.instance.SAMWithDefender
  SourceFile: "LambdaInstance.java"
  EnclosingMethod: #24.#25                // fr.doan.lambda.instance.LambdaInstance.createStatefullLambda
  InnerClasses:   static #10; //class fr/doan/lambda/instance/LambdaInstance$2
	   
  java.lang.String cap$0;
    flags: ACC_SYNTHETIC

  fr.doan.lambda.instance.LambdaInstance$2(java.lang.String);
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aload_1
         6: putfield      #2                  // Field cap$0:Ljava/lang/String;
         9: return
      LineNumberTable:
        line 15: 0
      ...
      ...
}

We clearly see at lines 7-8 that the compiler generated a synthetic field cap$0 (capture0) to capture the local variable and passed it to the constructor argument (line 10)

When executing the LambdaInstance class, the output shows:

I am fr.doan.lambda.instance.LambdaInstance$1@59dbd

I am fr.doan.lambda.instance.LambdaInstance$2@b6e768

That’s all folks!

 

The above scenario can be illustrated by a code example on GitHub at https://github.com/doanduyhai/Java8_Lambda_In_Details. Just execute the LambdaInstance.bat(LambdaInstance.sh) script

 
 

2 Comments

  1. Amit Phaltankar

    Hey, really a detailed and interesting set of articles.
    please visit http://amitrp.blogspot.in/2012/08/at-first-sight-with-closures-in-java.html where i have put a detailed explanations of Java Closures with working examples.

    Reply
  2. Opciones Binarias

    Hmm is anyone else having problems with the pictures on this blog loading?
    I’m trying to determine if its a problem on my end or if it’s the blog.

    Any suggestions would be greatly appreciated.

    Reply

Leave a Reply to Opciones Binarias Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.