Internal working of the functional interface and lambda expression
Hello developers, are you excited for the next blog? If yes then read along.
This blog is again from Java 8 and we are going to talk about Functional Interfaces and Lambda Expressions. Initially, we will look into the basics of what is a functional interface and lambda expression and how they are implemented, and later on, we will understand how they are compiled into the byte code.
The functional interface is an interface with one abstract method. But it can have other members such as default and static methods. But let us focus only on abstract methods. One of the functional interfaces in java is the Runnable interface. I hope you guys are familiar with it and must have used it many times in multi-threading. It has one abstract method — run(). Let us see how we can implement it but first let us understand lambda expression.
A lambda expression is an anonymous method that is used to implement the abstract method of the functional interface. It has three parts — First is the parameter list then the arrow (->) which is the new operator introduced in Java 8 and then we have the abstract method’s body.
Let us first see the traditional way of implementing an interface and later on we will the Java 8 version.
Prior to Java 8, we used to create a class that implements the abstract method of the interface. Here is an example.
This is how we implement an interface but don’t you think we create a lot of boilerplate code by creating an extra class just for the implementation. And what if I am only interested in the implementation of the abstract method and not the class who implements the abstract method. what if I want to pass my implementation to other methods or what if I am not interested in creating any class for an interface who is having just one single abstract method. I see we have a lot of questions coming up. Let us see how Java 8 addresses these questions.
Woah!!!, the above code looks much cleaner. I already got rid of an extra class that implements the Runnable interface and am directly using the implementation. The () brackets indicate method parameters but in this case, the run method doesn’t contain any. The -> new operator introduced by Java 8 followed by the System.out.println statement which becomes the method body for the run method. But how JVM handle such type of expression? Let us find out.
Whenever a java compiler translates a lambda expression into byte code, it copies the lambda’s body into a private method or private static method inside of the class in which expression is defined. The decision to make a private method or private static method totally depends on whether lambda expression is making use of any instance member or not. The above example doesn't make any use of instance members. Hence, it is copied into the static method. Such type of lambda expression is called Non-Capturing lambda. These private methods are named lambda$x$y where x represents the name of the method that contains the lambda expression and y represents a zero-based sequence number. The parameters of such a method are those of the functional interface that the lambda expression implements.
Prior to Java 8, there are two ways to implement an interface — One is to create a class and then implement the interface and the other one is to create an anonymous class that implements the interface. In both cases, an extra class file is generated at the compile time for the implementation.
In Java 7, a new JVM invokedynamic instruction was introduced. The main role of invokedynamic is to delay the creation of these extra classes until runtime. The invokedynamic does not have anything to do with class creation, it only allows to delay the decision of how to dispatch a method until runtime. At compile time, a lambda expression is replaced by an invokedynamic instruction.
Instructions other than invokedynamic such as invokevirtual, invokespecial, etc are meant specifically for statically typed constructs of the Java language ie the type of variables are clearly defined and it can be known at compile time as to what method/operations should be invoked for particular object/operands.
In the case of dynamically typed language, it is not known until runtime what method/invocations will be made. To counter it, we can assume a call site to also be an object representing the information/logic that is required to dynamically locate/construct and invoke methods/operations at runtime. This is represented by java.lang.invoke.CallSite.
This callsite acts as a container for the Method handle. Let us try to understand what is Method handles.
Method handles were introduced in Java 7 to work alongside the existing reflection APIs, as they serve different purposes and have different characteristics. A MethodHandle can represent a method, constructor, or field, they are not intended to describe properties of these class members. For example — it is not possible to directly extract metadata from a method handle such as modifiers or annotation values of the represented method. A method handle allows for the invocation of a referenced method, their main purpose is to be used together with an invokedynamic call site.
Method handles cannot be instantiated. Instead, method handles are created by using a designated lookup object. These objects are themselves created by using a factory method that is provided by the MethodHandles class. Whenever this factory is invoked, it first creates a security context that ensures that the resulting lookup object can only locate methods that are also visible to the class from which the factory method was invoked. The following example can help you to understand MethodHandles.
For bootstrapping a callsite, any invokedynamic instruction currently delegates to the LambdaMetaFactory class which is included in Java class library. This factory is then responsible for creating a class that implements the functional interface and which invokes the appropriate method that contains the lambda’s body which, as described before, is stored in the original class as private methods. In the future, this bootstrapping process might however change which is one of the major advantages of using invokedynamic for implementing lambda expressions. If one day, a better-suited language feature was available for implementing lambda expressions, the current implementation could simply be swapped out.
The invokedynamic instruction can be roughly represented as the following java code.
As you can see LambdaMetaFactory is used for producing callsite. The factory method returns an implementation of a functional interface using invokeExact. The implementation would roughly look like this
If the lambda has enclosed variables, then invokeExact accepts these variables as actual arguments. This approach is similar to anonymous classes in Java language with the following differences
- The anonymous class is generated by the Java compiler at compile-time
- The class for lambda implementation is generated by the JVM at runtime.
That’s all from this blog. I hope you guys understood the working of the functional interface and lambda expression.