When Java 8 was released in 2014, several new features geared towards increasing developer productivity were introduced along with it. The two most prominent enhancements were Lambda Expressions and Streams API. In tandem, these features offer an effective new way to approach Collections processing. In this post, I’ll cover lambda expressions, but be on the lookout for a future post covering the Streams API.
The Basics of Lambda
Lambda expressions, at their core, are a means to convey an entire method as a parameter. Java approached method parameterization by allowing you to pass around functional interface implementations in a concise manner. A functional interface contains a single abstract method that defines its overall purpose. You may be familiar with some interfaces that are already structured this way, such as Runnable. Lambda expressions represent the shorthand way of implementing these functional interfaces without having to verbosely override the interface every time.
Your First Lambda Expression
Let’s take a look at the simplified source code for one functional interface, Consumer, and then apply it concretely with a lambda expression.
Here’s an example lambda expression that implements Consumer:
Some initial observations:
- Parameter(s) are supplied on the left-hand side
- Statement(s) are placed on the right-hand side in a block
- An arrow representation (->) separates the parameters and statements
- A String is used to type the Consumer<T> in this specific example
- The example lambda expression matches with the method signature of void accept(T t)
- We are passing one parameter (String string)
- We are returning void in the statements block (System.out.println(string); )
If you are familiar with the Groovy programming language, you might notice the similarities between Java lambda expressions and Groovy closures. An equivalent Groovy closure to the lambda expression above is:
Indeed, both lambda expressions and closures exist to allow behavior parameterization. If your current project will not allow the use of Groovy, you can still emulate the advantages of Groovy closures by using Java lambda expressions.
Improving Your Existing Code
The utility and flexibility of lambda expressions become more apparent every time you use them; imagine you have multiple methods that work with a single file:
The File method examples above contain some boilerplate preparatory code; we repeatedly verify that the file is not a null reference and actually exists in the file system. It would be nice to have a single method with the preparatory code that accepts custom behavior as a parameter. If you could do this, you wouldn’t violate the “Don’t Repeat Yourself” principle so often. Every time you create another File method like those above with only slightly differing functionality, the complexity and repetition in your code grows.
With lambda expressions, you can parameterize the custom portions of these methods. To do this, we create the following method and use the Consumer functional interface:
Now you can remove the other methods and perform virtually any action you want with the behaviorally parameterized method, supplying a lambda expression for the Consumer parameter:
Taking Things Further
There are many more functional interfaces that Java 8 supplies other than Consumer:
- Predicate<T>, which accepts an object T and returns a primitive boolean
- Supplier<T>, which has no parameters and returns an object T
- Function<T, R>, which accepts an object T and returns an object R
The entire list of functional interfaces available in Java 8 can be found here.
Lambda expressions showcase Java’s evolution to accommodate functional programming. The ability to be able to pass methods as easily as objects can broaden your design options, especially when requirements can change on a whim. While behavior parameterization was an option before Java 8 with anonymous implementations of classes, it was verbose and often too specialized. Java 8 functional interfaces and lambda expressions are excellent additions to your repertoire for handling common tasks in a concise and flexible manner.