Let’s take a look into lambda expressions as part of the Java language specifiction, what they are, how they are represented, and how existing bytecode can be backported to earlier class files versions without losing any functionality.
A lambda expressions is basically syntactic sugar that allows you to define inline anonymous classes that implement specific interfaces. Take a look at the following example:
In this example we set a click listener for a specific button using a lambda expression. This is equivalent to writing this code:
The use of lambda expressions makes the whole code a bit more concise and less bloated. I dont want to go into more details on how to write lambda expressions in the java language. There are many resources out there for this purpose. In this blog post I would like to focus on how such lambda expressions are represented in byte code, and how it can be backported to earlier class file versions, which is important when targeting platforms like Android.
Let’s analyse how the javac compiler translates this java code into the corresponding class file format:
We can see that an instruction of type invokedynamic is executed which returns an object of type OnClickListener and passes this on to the setOnClickListener method of the Button object. The invokedynamic instruction calls the BootstrapMethod #0 as defined in the class:
The definition of this BootstrapMethod contains information about the actual method to be called, its parameter and return types. The JVM will then dynamically create at runtime a CallSite targeting the specified target method and execute it.
In our case the target method is also stored in the class file itself as private method (with the name as specified in the BootstrapMethod):
Now this is the most simple example of a lambda expression. There are different cases to consider (e.g. capturing or non-capturing, references to constructors, …). I don’t want to go into too much detail here, lets dive into ways to backport these constructs in a way that they can be represented in earlier class file versions.
There is a very popular and amazing tool called retrolambda which does exactly that, backport any lambda expression to java version 5, 6 or 7. For various technical reasons, I had the need to implement such a mechanism myself as the product I have been working on (ProGuard / DexGuard) also had to work in full standalone mode and is generally self-contained, thus has no external dependencies.
So the basic idea is to extract the generated private method into a separate lambda class that implements the necessary interface(s). Then replace the invokedynamic instruction calls in a way to create an instance of this lambda class.
The code to perform this is open-source and accessible at the ProGuard github repo.
Applied on the above example, the result would look like in the snippets below. An additional accessor method has been added to avoid modifying the visibility of the lambda methods, but that is not a technical necessity its rather a design choice.
and the corresponding generated lambda class:
Of course, to fully support all possible variants of lambda expressions (and its sibling method references) some effort has to be put forward, but the necessary code is quite limited, take a look at the referenced source code, especially the class LambdaExpressionConverter. ProGuard offers a lot of functionality to make any kind of byte code engineering quite simple and effective.
In this blog post I am focusing on lambda expressions, but ProGuard is capable of backporting various other language features of Java, like static interface methods, default methods, try-with-resources, string concatenations introduced in Java 9 and even the use of Java 8 APIs like the popular stream and time APIs.
Take a look at the source code, its very clean and can easily be read and understood imho.
No comments found for this article.
Join the discussion for this article on this ticket. Comments appear on this page instantly.