Image Blog Java 8 the First Taste of Lambdas
February 5, 2013

Exploring Java 8 Lambdas

Java Tools
Java Updates

Project Lambda is the major theme in Java 8 and probably the most awaited one among Java developers. There's one interesting feature that is being added to Java along with lambdas - the defender methods. In this blog post my intention is to look under the covers – to see how Java 8 lambdas are represented by the runtime and what bytecode instructions are involved during method dispatch.

So You Want to Use Lambdas in Java 8?

If you are familiar with other languages that include lambda expressions, such as Groovy or Ruby, you might be surprised at first that it is not as simple in Java.

What Is a Java Lambda?

In Java, lambda expression is "SAM type", which is an interface with a single abstract method (yes, interfaces can include non-abstract methods now - the defender methods).

So for instance the well known Runnable interface is perfectly suitable for serving as a SAM type:


Runnable r = () -> System.out.println("hello lambda!");


Or the same could be applied to the Comparable interface:


Comparator cmp = (x, y) -> (x < y) ? -1 : ((x > y) ? 1 : 0);


The same can be written as follows:


Comparator cmp = (x, y) -> {
  return (x < y) ? -1 : ((x > y) ? 1 : 0);
};


So it seems like the one-liner lambda expressions have implicit return for the statement.

What if I want to write a method that can accept a lambda expression as a parameter? Well, you have to declare the parameter as a functional interface, and then you can pass the lambda in:


interface Action {
   void run(String param);
}

public void execute(Action action){
   action.run("Hello!");
}


Once we have a method that takes a functional interface as a parameter we can invoke it as follows:


execute((String s) -> System.out.println(s));


Effectively, the same expression can be replaced with a method reference since it is just a single method call with the same parameter:


execute(System.out::println);


However, if there's any transformations going on with the argument, we can't use method references and have to type the full lambda expression out:


execute((String s) -> System.out.println("*" + s + "*"));


I think the syntax is rather nice and we now have quite an elegant solution for lambdas in Java 8 -- despite the fact Java doesn’t have functional types per se.

Functional Interfaces in JDK8

As we learned, the runtime representation of a lambda is a functional interface (or a "SAM type"), an interface that defines only one abstract method. And although JDK already includes a number of interfaces, like Runnable and Comparable, that match the criteria, it is clearly not enough for API evolution. It just wouldn’t be as logical if we started using Runnables all around the code.

There’s a new package in JDK8, java.util.function, that includes a number of functional interfaces that are intended to be used by the new API. We won’t list all of them here – just do yourself a favour and study the package yourself :)

It seems that the library is evolving quite actively as some interfaces come and go. For instance it used to provide java.util.function.Block class which isn't present in the latest build that I have at the time of writing this post:

anton$ java -version
openjdk version "1.8.0-ea"
OpenJDK Runtime Environment (build 1.8.0-ea-b75)
OpenJDK 64-Bit Server VM (build 25.0-b15, mixed mode)


As I discovered it is now replaced with Consumer interface and is used for all the new methods in the collections library. For instance, the Collection interface defines the forEach method as follows:


public default void forEach(Consumer consumer) {
  for (T t : this) {
    consumer.accept(t);
  }
}


The interesting bit of the Consumer interface is that it actually defines one abstract method - accept(T t), and a defender method - Consumer<T> chain(Consumer<? extend T> consumer). It means that it is possible to chain the calls using this interface. I'm not quite sure how it will be used since I couldn't find chain(..) method usages in the JDK library yet.

Also, I noticed that all the interfaces are marked with @FunctionalInterface runtime annotation. But aside its runtime presence the annotation is used by javac to verify if the interface is really a functional interface and there's no more than one abstract method in it.

So if I try to compile code like this:


@FunctionalInterface
interface Action {
  void run(String param);
  void stop(String param);
}


The compiler will tell me:

java: Unexpected @FunctionalInterface annotation
  Action is not a functional interface
	multiple non-overriding abstract methods found in interface Action


While the following will compile just fine:


@FunctionalInterface
interface Action {
  void run(String param);
  default void stop(String param){}
}

 

Decompiling Lambdas in Java 7

I'm usually not as curious about the syntax and language features as I am about the runtime representation of those features. That is why one natural thing for me was to grab my favourite javap utility and start reading the bytecode of the classes that include lambdas.

Currently (as of Java 7 and before), if you wanted to emulate lambdas in Java, you have to define an anonymous inner class. This results in a dedicated class file after compilation. And if you have multiple such classes defined in the code they just get a number suffix in the name of the class file. What about lambdas?

Consider code like this:


public class Main {

  @FunctionalInterface
  interface Action {
    void run(String s);
  }

  public void action(Action action){
    action.run("Hello!");
  }

  public static void main(String[] args) {
    new Main().action((String s) -> System.out.print("*" + s + "*"));
  }

}


The compilation produces two class files: Main.class and Main$Action.class, and no numbered class which would usually appear for the anonymous class implementation. So there must be something in Main.class now that represents the implementation of the lambda expression that I've defined in main method.

$ javap -p Main 

Warning: Binary file Main contains com.zt.Main
Compiled from "Main.java"
public class com.zt.Main {
  public com.zt.Main();
  public void action(com.zt.Main$Action);
  public static void main(java.lang.String[]);
  private static java.lang.Object lambda$0(java.lang.String);
}


Aha! There's a generated method lambda$0 in the decompiled class! The -c -v switches will give us the real bytecode along with the constants pool definitions.

The main method reveals that invokedynamic is now issued to dispatch the call:

public static void main(java.lang.String[]);
  Code:
   0: new               #4    // class com/zt/Main
   3: dup          
   4: invokespecial #5        // Method "":()V
   7: invokedynamic #6,  0    // InvokeDynamic #0:lambda:()Lcom/zt/Main$Action;
   12: invokevirtual #7       // Method action:(Lcom/zt/Main$Action;)V
   15: return        


And in the constant pool it is possible to find the bootstrap method that links it all at runtime:

BootstrapMethods:
  0: #40 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #41 invokeinterface com/zt/Main$Action.run:(Ljava/lang/String;)Ljava/lang/Object;
    #42 invokestatic com/zt/Main.lambda$0:(Ljava/lang/String;)Ljava/lang/Object;
    #43 (Ljava/lang/String;)Ljava/lang/Object;


You can see that MethodHandle API is used all around but we won't dive in this right now. For now we can just confirm that the definition refers to the generated method lambda$0.

I was curious, what if I define my own static method with the same name - "lambda$0" is a valid identifier after all! So I defined my own lambda$0 method:


public static Object lambda$0(String s){ return null; }


With this compilation failed, not allowing me to have this method in the code:

java: the symbol lambda$0(java.lang.String) conflicts with a 
           compiler-synthesized symbol in com.zt.Main


At the same time, if I removed the piece of code that defined lambda expression, the code compiled just fine. It actually tells us that lambdas are captured before the other structured in the class during the compilation, but that's only my assumption.

Please note that in this example the lambda did not capture any of the variables and did not refer to any methods of the enclosing class. That is why the generated lambda$0 method is static. If the lambda refers to any of the enclosing context variables or methods, then a non-static method is generated. So don't be mislead by the simple example - lambdas can capture enclosing context just fine!

Summary

We can definitely say that lambdas in Java 8 and the accompanied features (defender methods, collections library improvements) will have a great impact on Java very soon. The syntax is quite nice and once developers realize that these features provide value to their productivity, we will see a lot of code that leverages these features. It was quite interesting for me to see what lambdas are compiled to and I was very happy when I saw the total utilization of the invokedynamic instruction in action without any anonymous inner classes involved at all.

Additional Resources

Want to learn more about the latest features in Java? Be sure to visit our new resource, Exploring New Features in Java. It has in-depth articles on many of the major releases for Java 8 to the latest releases.

If you're working in Java 8, be sure to check out our Java 8 Streams Cheat Sheet. Get definitions, examples, useful operations and more by clicking the button below.

Get the Cheat Sheet