What does a lambda expression look like inside Java code and inside the JVM? It is obviously some type of value, and Java permits only two sorts of values: primitive types and object references. Lambdas are obviously not primitive types, so a lambda expression must therefore be some sort of expression that returns an object reference.
A lambda expression is a short block of code that takes in some input (parameters) and returns a value. You can think of lambda expressions as being like methods. Just like methods, they can take parameters and give us return values. However, unlike regular methods, lambda expressions don’t need a name. As a result, they can be implemented right in the body of a method.
- The lambda expression is a new and important feature of Java that was introduced in Java SE 8.
- It provides a concise way to represent an anonymous function, which can be used to implement the functional interface.
- Lambda expressions can be used to iterate, filter, and extract data from collections.
Lambda expressions were introduced in Java 8 to allow you to implement one function interface. They are anonymous functions that add functional programming techniques to Java. Writing code in a specific function is easier than using anonymous inner classes.
Advancement in technology occurs every single day. Lambda expression in Java is a new additional extension in Java 8. With domain-expertise and experience in Java, As Java development company we have prepared this insightful blog on the use of Lambda expressions with functional interfaces and instances that will make users well-versed with Java lambda expressions.
We do not have to define the Lambda expression. It is an anonymous method that does not execute itself. It can only be implemented if you define a functional interface and run that method. Lambda can be considered as methods that are implemented in the body section of the method.
Lambda expression was one of the most requested features which were made available since Java SE 8. A Java Lambda expression is a function that does not belong to any class. Java lambda expressions are commonly used to implement simple event listeners.
What does a lambda expression look like inside Java code and inside the JVM? It is obviously some type of value, and Java permits only two sorts of values: primitive types and object references. Lambdas are obviously not primitive types, so a lambda expression must therefore be some sort of expression that returns an object reference.
Let’s look at an example:
public class LambdaExample {
private static final String HELLO = "Hello World!";
public static void main(String[] args) throws Exception {
Runnable r = () -> System.out.println(HELLO);
Thread t = new Thread(r);
t.start();
t.join();
}
}
Why Do We Use Lambda Functions?
You may wonder why we use lambda expressions in Java. There are certain use cases where they can be very useful.
Some people say that makes use of anonymous functions more concise and readable, which is obvious and noticeable, but the primary objective of lambda expressions is functional programming. By using Lambda expressions, you can write cleaner code, which in turn improves readability by eliminating the need for verbose anonymous inner class declarations.
- To provide the implementation of a functional interface.
- To improve readability by writing the code without the need for defining separate named functions.
- To reduce the codebase by writing modular code and reusable code
- To improve the collection libraries, making it easier to iterate through, filter, and extract data from a collection.
1. Functional Interfaces
Lambda expressions provide a convenient way to implement the abstract method of a functional interface directly inline, avoiding the need to create separate classes or instances.
We can take the Java Runnable interface as an example. It has only one abstract method, run().
Take an example of the below code has only one method GiveName defined that makes it a functional interface:
interface interface1{
String GiveName(String str);
}
2. Collections and Streams
When working with collections, streams, and other data processing operations, lambda expressions can be used to concisely define transformation and filtering functions.
3. Event Handling
In GUI applications and other event-driven scenarios, lambda expressions can be used to simplify the definition of event listeners and handlers. This is because you can define them inline without the need to create separate classes for each event handler.
4. Parallelism
Lambda expressions are closely associated with Java’s Stream API, which can be used for parallel processing of data. Lambda expressions make it easier to specify the operations to be applied to individual elements in a parallelized manner.
5. Functional Programming Concepts
If you are interested in functional programming concepts such as first-class functions, higher-order functions, and closures, lambda expressions can be used to implement these concepts in Java code.
Both these versions are external iterators, which mix how you do it with what you would like to achieve. You explicitly control the iteration with them, indicating where to start and where to end; the second version does that under the hood using the Iterator
methods. With explicit control, the break
and continue
statements help manage the iteration’s flow of control.
The second construct has less ceremony than the first and is better than the first if you don’t intend to modify the collection at a particular index. Both styles, however, are imperative and you can dispense with them in modern Java by using a functional approach.
There are quite a few reasons to favor the change to the functional style.
for
loops are inherently sequential and are quite difficult to parallelize.- Such loops are nonpolymorphic: You get exactly what you ask for. You passed the collection to
for
instead of invoking a method (a polymorphic operation) on the collection to perform the task. - At the design level, the code fails the “Tell, don’t ask” principle. You ask for a specific iteration to be performed instead of leaving the details of the iteration to the underlying libraries.
It’s time to trade in the old imperative style for the more elegant functional-style version of internal iteration. With an internal iteration, you willfully turn over the how to the underlying library so you can focus on the essential what. The underlying function will take care of managing the iteration.
Here, you will use an internal iterator to enumerate the names. The Iterable
interface has been enhanced (beginning in JDK 8) with a special method named forEach()
, which accepts a parameter of type Consumer
. As the name indicates, an instance of Consumer
will consume, through its accept()
method, which is what’s given to it. Use the forEach()
method with the anonymous inner class syntax.
friends.forEach(new Consumer<String>() {
public void accept(final String name) {
System.out.println(name);
}
});
That’s a lot better, and not only because it is shorter. The forEach()
is a higher-order function that accepts a lambda expression or block of code to execute in the context of each element in the list. The variable name
is bound to each element of the collection during the call. The underlying library takes control of how any lambda expressions are evaluated. It can decide to perform them lazily, in any order, and exploit parallelism as it sees fit.
This version produces the same output as the previous versions. The internal-iterator version is more concise than the other ones. In addition, it helps focus your attention on what you want to achieve for each element rather than how to sequence through the iteration—it’s declarative.
The better-code version has a limitation, however. Once the forEach()
method starts, unlike in the other two versions, you can’t break out of the iteration. (There are facilities to handle this limitation.) Consequently, this style is useful where you want to process each element in a collection. Later I’ll show other functions that offer more control over the path of iteration.
The standard syntax for lambda expressions expects the parameters to be enclosed in parentheses, with the type information provided and comma separated. The Java compiler also offers some lenience and can infer the types. Leaving out the type is convenient, requires less effort, and is less noisy. Here’s the previous code without the type information.
friends.forEach((name) -> System.out.println(name));
Lambda Parameters
Since Java lambda expressions are just methods, lambda expressions can also take parameters just like methods. These parameters must be the same count and same data type as the parameters of the method in the single method interface.
Zero Parameter
- If the matching method from the interface does not have any parameter then you can write a Lambda expression like,
() -> { System.out.println("method body"); }; |
- If the method body has only one line then you can also emit curly braces.
() -> System.out.println("method body"); |
Single Parameter
- If interface method has one parameter then Lambda expression will be like this,
(param) -> {System.out.println("One parameter: " + param);}; |
- For one parameter, you can also omit parentheses,
param -> System.out.println("One parameter: " + param); |
Multiple Parameter
- If the method you match with your Java lambda expression takes multiple parameters, the parameters need to be listed in parentheses. Here is how that looks in Java code:
(p1, p2) { System.out.println("Multiple parameters: " + p1 + ", " + p2); }; |
The most straightforward way to understand the invokedynamic
call in this code is to think of it as a call to an unusual form of the factory method. The method call returns an instance of some type that implements Runnable
. The exact type is not specified in the bytecode and it fundamentally does not matter.
The actual type does not exist at compile time and will be created on demand at runtime. To better explain this, I’ll discuss three mechanisms that work together to produce this capability: call sites, method handles, and bootstrapping.
Call sites
A location in the bytecode where a method invocation instruction occurs is known as a call site.
Java bytecode has traditionally had four opcodes that handle different cases of method invocation: static methods, “normal” invocation (a virtual call that may involve method overriding), interface lookup, and “special” invocation (for cases where override resolution is not required, such as superclass calls and private methods).
Dynamic invocation goes much further than that by offering a mechanism through which the decision about which method is actually called is made by the programmer, on a per-call site basis.
Here, invokedynamic
call sites are represented as CallSite
objects in the Java heap. This isn’t strange: Java has been doing similar things with the Reflection API since Java 1.1 with types such as Method
and, for that matter, Class
. Java has many dynamic behaviors at runtime, so there should be nothing surprising about the idea that Java is now modeling call sites as well as other runtime type information.
When the invokedynamic
instruction is reached, the JVM locates the corresponding call site object (or it creates one, if this call site has never been reached before). The call site object contains a method handle, which is an object that represents the method that I actually want to invoke.
The call site object is a necessary level of indirection, allowing the associated invocation target (that is, the method handle) to change over time.
There are three available subclasses of CallSite
(which is abstract): ConstantCallSite
, MutableCallSite
, and VolatileCallSite
. The base class has only package-private constructors, while the three subtypes have public constructors. This means that CallSite
cannot be directly subclassed by user code, but it is possible to subclass the subtypes. For example, the JRuby language uses invokedynamic
as part of its implementation and subclasses MutableCallSite
.
Note: Some invokedynamic
call sites are effectively just lazily computed, and the method they target will never change after they have been executed the first time. This is a very common use case for ConstantCallSite
, and this includes lambda expressions.
This means that a nonconstant call site can have many different method handles as its target over the lifetime of a program.
Method handles
Reflection is a powerful technique for doing runtime tricks, but it has a number of design flaws (hindsight is 20/20, of course), and it is definitely showing its age now. One key problem with reflection is performance, especially since reflective calls are difficult for the just-in-time (JIT) compiler to inline.
This is bad, because inlining is very important to JIT compilation in several ways, not the least of which is because it’s usually the first optimization applied and it opens the door to other techniques (such as escape analysis and dead code elimination).
A second problem is that reflective calls are linked every time the call site of Method.invoke()
is encountered. That means, for example, that security access checks are performed. This is very wasteful because the check will typically either succeed or fail on the first call, and if it succeeds, it will continue to do so for the life of the program. Yet, reflection does this linking over and over again. Thus, reflection incurs a lot of unnecessary cost by relinking and wasting CPU time.
To solve these problems (and others), Java 7 introduced a new API, java.lang.invoke
, which is often casually called method handles due to the name of the main class it introduced.
A method handle (MH) is Java’s version of a type-safe function pointer. It’s a way of referring to a method that the code might want to call, similar to a Method
object from Java reflection. The MH has an invoke()
method that actually executes the underlying method, in just the same way as reflection.
At one level, MHs are really just a more efficient reflection mechanism that’s closer to the metal; anything represented by an object from the Reflection API can be converted to an equivalent MH. For example, a reflective Method
object can be converted to an MH using Lookup.unreflect()
. The MHs that are created are usually a more efficient way to access the underlying methods.
MHs can be adapted, via helper methods in the MethodHandles
class, in a number of ways such as by composition and the partial binding of method arguments (currying).
Normally, method linkage requires exact matching of type descriptors. However, the invoke()
method on an MH has a special polymorphic signature that allows linkage to proceed regardless of the signature of the method being called.
At runtime, the signature at the invoke()
call site should look like you are calling the referenced method directly, which avoids type conversions and autoboxing costs that are typical with reflected calls.
Because Java is a statically typed language, the question arises as to how much type-safety can be preserved when such a fundamentally dynamic mechanism is used. The MH API addresses this by use of a type called MethodType
, which is an immutable representation of the arguments that a method takes: the signature of the method.
The internal implementation of MHs was changed during the lifetime of Java 8. The new implementation is called lambda forms, and it provided a dramatic performance improvement with MHs now being better than reflection for many use cases.
Bootstrapping
The first time each specific invokedynamic
call site is encountered in the bytecode instruction stream, the JVM doesn’t know which method it targets. In fact, there is no call site object associated with the instruction.
The call site needs to be bootstrapped, and the JVM achieves this by running a bootstrap method (BSM) to generate and return a call site object.
Each invokedynamic
call site has a BSM associated with it, which is stored in a separate area of the class file. These methods allow user code to programmatically determine linkage at runtime.
Decompiling an invokedynamic
call, such as that from my original example of a Runnable
, shows that it has this form:
0: invokedynamic #2, 0
The presence of 0 in the constant is a clue. Constant pool entries are numbered from 1, so the 0 reminds you that the actual BSM is located in another part of the class file.
For lambdas, the NameAndType
entry takes on a special form. The name is arbitrary, but the type signature contains some useful information.
The return type corresponds to the return type of the invokedynamic
factory; it is the target type of the lambda expression. Also, the argument list consists of the types of elements that are being captured by the lambda. In the case of a stateless lambda, the return type will always be empty. Only a Java closure will have arguments present.
A BSM takes at least three arguments and returns a CallSite
. The standard arguments are of these types:
MethodHandles.Lookup
: A lookup object on the class in which the call site occursString
: The name mentioned in theNameAndType
MethodType
: The resolved type descriptor of theNameAndType
Following these arguments are any additional arguments that are needed by the BSM. These are referred to as additional static arguments in the documentation.
The general case of BSMs allows an extremely flexible mechanism, and non-Java language implementers use this. However, the Java language does not provide a language-level construct for producing arbitrary invokedynamic
call sites.
For lambda expressions, the BSM takes a special form and to fully understand how the mechanism works, I will examine it more closely.
Transforming a list
Manipulating a collection to produce another result is as easy as iterating through the elements of a collection. Suppose the task is to convert a list of names to all capital letters. What are some options?
Java’s String
is immutable, so instances can’t be changed. You could create new strings in all caps and replace the appropriate elements in the collection. However, the original collection would be lost; also, if the original list is immutable, as it is when it’s created with Arrays.asList()
, the list can’t change. It would also be hard to parallelize the work.
Creating a new list that has the elements in all uppercase is better.
That suggestion may seem quite naive at first; performance is an obvious concern for everyone. You’re likely to find, however, that the functional approach often yields surprisingly better performance than the imperative approach. Start by creating a new collection of uppercase names from the given collection.
final List<String> uppercaseNames =
new ArrayList<String>();
for(String name : friends) {
uppercaseNames.add(name.toUpperCase());
}
Accessible Variables
A Java lambda expression is capable of accessing variables declared outside the lambda function body under certain circumstances.
Java lambda expression can access the following types of variables:
- Local variables
- Instance variables
- Static variables
Local Variables
- A Lambda expression can access the value of a local variable declared outside the lambda body if and only if the variable is “effectively final” or declared as final.
- If you try to change the value of that variable the compiler would give you an error about the reference to it from inside the lambda body.
Effectively Final
- It means that the variable is not declared as final but its value has never been changed once it is assigned.
Instance Variables
- A Lambda expression can also access an instance variable. Java Developer can change the value of the instance variable even after its defined and the value will be changed inside the lambda as well.
Static Variables
- A Lambda expression can also use static variables. Because a static variable is accessible from everywhere in a Java application. Static variable can be changed once it is used in Lamda expression by the Java developer.