It’s not a great surprise that even when I am using many different programming techniques, programming languages, and libraries, I barely know five percent of them in a deep way. The fact is, it’s not a major problem, the real problem is that I only know the bare minimum about half of Java’s core libraries and am only using them as API s—Lambda expressions fall into this category. On top of which, whenever there is a talk about Lambda expressions , I get the feeling that everyone knows more about them than me. This is something I intend to change.
Lambda expressions are great in my eyes, but recently I discovered my inability to apply or write them without the help of an intelligent IDE ( Intellij IDEA in my case). They have become only syntactic sugar for me (for more than two years, huh), and I wasn’t diligent enough to find out how they actually might work. So I have begun to study them and here in this post I have collected some of my reflections. I’ll not include any of code examples as you can find plenty of them following the links throughout the article.
When Has the Story Begun?
There have been discussions about lambdas for quite some time; it started in 2006 (yes, ten years ago). After flame wars, both for and against, Oracle decided in 2010 that Java is not dead and wanted to keep it a modern language that can adapt to the trends, hardware, and future platforms. I would say that the main reason why Oracle incorporated the Lambda expressions into Java was to facilitate concurrency for the masses rather than providing syntactic sugar.
Everyone talks about the improvements inside the Collection framework and enabling parallelization for processing of collections in an easy, built-in way. But it’s not the only benefit which we can see and use.
What Are the Lambda Expressions?
The best matching expression for me is – it’s an anonymous piece of functionality. This functionality we can pass as an argument to the existing operations (methods in Java). We can glance at them as the shorter form of creation of anonymous inner classes which we were used for passing code to the methods – for this statement is used the term "code as data".
Two advantages. With anonymous inner classes we, in a fact, passing into the method the created object. This object creation is no longer needed; we are passing the lambda expression only. The second advantage is the syntactic overhead of anonymous inner classes is no longer needed. The Lambda expressions are more convenient, less boilerplate code, and more concise.
Where the Lambda Expressions Come From
All too often these two words are used as synonyms, mainly because both are representing a piece of functionality, they both takes the arguments, have a body and are executable, producing the outputs and may produce side effects.
Methods belong to objects and are mostly modifying fields owned by the object. Methods are used in the imperative order; the developer is saying how things are done.
In comparison, the functions are used in declarative style; they describe what a program should do instead of how. They do not mutate any data. Instead, they produce the results concerning the passed arguments and functions can be passed to the other operations as arguments.
Why We Should Use the Lambda Expressions
- They are concise.
- Readability – you don’t need to look elsewhere for a method’s definition.
- Internal collection iteration over external loops is more efficient and enables parallelization of execution.
- When some functionality is used in only one place.
- To avoid boilerplate code using execute-aroung design pattern .
Interesting Implementation Details
- There is no longer a need to mark variables from enclosing scope as final—they are effectively marked as final by the compiler when used in the lambda expression. ** Should we still make it final for readability purposes? It depends on the situation, but I’m inclined to make the effectively final variables also explicitly marked with using final.
- The lambda expression can be utilized only for interfaces which are containing the single abstract method. But the default methods are allowed, and such an interface we can use in the lambda. Of course, we should treat the default methods as a compromise when we need to enhance some interface and have no more elegant solution in our hands.
- The lambda has the same scope as the enclosing context – so you can’t, for example, redefine a variable from the enclosing context inside the lambda.
- this and super means the same thing in the lambda as they mean in the enclosing context.
- Execute-around-method pattern is a useful thing for reducing code duplication. By leveraging lambdas, we can pass code into a method where something is done before and after such passed code.
- For example, we can pass code into a try-and-catch sequence. Or even a stream’s forEach is an example of the execute-around pattern; it is a loop that executes around the lambda expression we passed in.
- If you are returning a value from a lambda using the “return” keyword for it, you need to enclose the body of the lambda into braces. You don’t have to enclose void statements. You don’t have to enclose returning statements without the“return” keyword.
- In lambda expressions you’re not specifying the target type (the type which the method is expecting to be passed in). Resolution of the target type is possible thanks to overload resolution and type argument inference.
- Maybe it’s surprising, but lambda expressions were never intended to replace anonymous classes entirely, only for certain scenarios. So we can only use lambdas with functional interfaces and not with abstract classes which can have SAM (single abstract method).
- Other Java 8 syntactic sugar—method references—also increases the readability of the lambda expressions.
- Type inference is a great thing. And therefore, you don’t need to specify the parameter types. Specification of the types was my first question about lambda—how the compiler knows which parameter is of which type. And this is possible just by the type inference—we should avoid specifying these types to increase readability and avoid boilerplate code.
- Overloading methods with the functional interfaces should be avoided because the compiler isn’t capable of distinguishing between them at runtime (but they are completely ok when we were defining them). Just use another method name or you need to cast the lambda expression before its definition.
- For clarity by using the @FunctionalInterface annotation, you can explicitly mark the interface as the functional one. It’s very helpful in large codebases when another developer might be tempted to enhance the interface with other functionality but would render the lambda expressions unusable. It’s an optional interface with no runtime impact but increases readability and makes the intent of the interface clear. Also, it helps the compiler to identify violations of such design intent.
- Functional interfaces from Java
- Comprehensive article about the Lambda expression
- JDK documentation about lambda
- JDK method reference documentation
- Comparison of performance the lambdas against anonymous inner classes
- Useful discussion about performance the lambda expressions
Wrapping it up, lambdas are very useful, concise and more convenient to use in comparison to anonymous inner classes. Apart from in some unusual cases, the performance of lambdas is similar or better than that of anonymous inner classes. When the body of a lambda expression has no more than three lines, their use is preferable. Lambda expressions allow a new way of treating Java collections—streams. Lambdas are now widely used in a broad range of cases throughout the entirety of most codebases.
P.S.If you enjoyed this post, you can share this post anywhere as well as follow me on twitter to stay in touch with my further articles and other thoughts.
P.S.2Cover image by Joshua Ness | unsplash.com .