Assistant Engineer
Assistant Engineer
  • UID622
  • Fans3
  • Follows0
  • Posts52

A primary study on Java Lambda expressions

More Posted time:Nov 8, 2016 13:50 PM
This article is inspired by Trisha Gee's keynote speech at JavaOne 2016 titled Refactoring to Java 8.
Java 8 has been released for more than two years, but many are still using JDK7. Being prudent technically is not always bad for enterprises. But for individuals, ignorance of new technologies may leave their careers out in the cold. One important update in Java 8 is the introduction of **Lambda expressions**. It sounds so awesome, despite my incomprehension of what Lambda expressions are. Don't let the name terrify you. Language-wise, Lambda expressions are just a new kind of syntax. With Lambda expressions, Java will open the gate to functional programming.
Why Lambda expressions
Don't get bogged down on what *Lambda* expressions or functional programming are. Let's first look at the convenience brought about by the new syntactic features of Java 8. I believe you will be impressed.
Before the *Lambda* expressions are available, you need to write the following code to create a thread:
new Thread(new Runnable(){
    public void run(){
        System.out.println("Thread run()");

While with *Lambda* expressions, you can write the code like this:
new Thread(
        () -> System.out.println("Thread run()")

As you can see, the previous useless template code is gone. As shown above, **a common usage of Lambda expressions is to replace (part of) anonymous internal classes**. But Lambda expressions can do more than this.
Principle of Lambda expressions
New to *Lambda* expressions, you may find them so magic: *functions can be defined directly without the need to declare the class or method name*. This feature seems like a trick provided by the compiler for simpler writing of anonymous internal classes, but in reality it is hardly ever the case. *Lambda* expressions are actually implemented using *invokedynamic* commands. Leave the details for now. The following are several possible forms of writing *Lambda* expressions which “look” not that hard to comprehend.
Runnable run = () -> System.out.println("Hello World");// 1
ActionListener listener = event -> System.out.println("button clicked");// 2
Runnable multiLine = () -> {// 3
    System.out.println("Hello ");
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5

From the example above, we can find that:
• Lambda expressions are type-sensitive. The type is on the left of the value assignment operation. Lambda expressions types are actually the **types of corresponding interfaces**.
• Lambda expressions can contain multiple lines of code. You need to enclose the code blocks in braces, as you do for writing function bodies.
• In most cases, arguments of Lambda expressions can omit the type, like in Code 2 and 5. This benefits from the **type inference** mechanism of javac. The compiler can infer the type according to the context.
It appears that every *Lambda expression is a brief form of the original anonymous internal class which implements a certain **functional interface**. But the reality is somewhat more complicated and I will not elaborate on it here. The so-called functional interface refers to an interface with a @FunctionalInterface label and contains only one internal interface function. Java is a strongly-typed language. No matter whether explicit specification is made, every variable and object must have a clear type. When no explicit specification is made, the compiler will try to identify the type.** The types of Lambda expressions are the types of their corresponding functional interfaces**.
Lambda expressions and Stream
Another usage of Lambda expressions is to be used in combination with Stream. Stream is a sequence of elements supporting sequential and parallel aggregate operations. These operations are specified through *Lambda* expressions. You can regard Stream as a kind of view of Java Collection, as in the case that the iterator is a kind of view of the container (but *Stream* won't modify the content in the container). The example below demonstrates the common usages of *Stream*.
Example 1
Supposing we need to select strings starting with a digit from a string list and output the conforming strings, before Java 7, the code should be like this:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
for(String str : list){

But in Java 8, you can write it like this:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
list.stream()// 1. Get the Steam of the container
    .filter(str -> Character.isDigit(str.charAt(0)))// 2. Select the strings starting with a digit
    .forEach(str -> System.out.println(str));// 3. Output the strings

In the code above, 1. Call the List.stream() method to get the *Stream* of the container; 2. Call the filter() method to filter out strings starting with a digit; 3. Call the forEach() method to output the result.
There are two obvious advantages for using Stream:
1. The template code is reduced. Only Lambda expressions are used to specify the required operations. The code syntax is clearer and easier to read.
2. External iteration is changed to the internal iteration of Stream, which greatly facilitates the optimization to the iteration process by JVM (for example, concurrent iteration can be available).
Example 2
Suppose we need to select all the strings not starting with a digit from a string list, convert them into the capitalized form, and put the result in a new set. The code in Java 8 is as follows:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
Set<String> newList =
        list.stream()// 1. Get the Stream of the container
        .filter(str -> !Character.isDigit(str.charAt(0)))// 2. Select the strings not starting with a digit
        .map(String::toUpperCase)// 3. Convert the strings into the capitalized form
        .collect(Collectors.toSet());// 4. Generate the result set

In the code above, 1. Call the List.stream() method to get the *Stream* of the container; 2. Call the filter() method to filter out strings starting with a digit; 3. Call the map() method to convert the strings into the capitalized form; 4. Call the collect() method to convert the result into Set. This example also demonstrates the usage of **method references** (Mark 3 in the code) and **Collector** (Mark 4 in the code). I will not go into details here.
Through this example, we see the chained operations of *Stream*, that is, multiple operations can be linked up to form a chain. Don't worry about whether it will lead to multi-iteration to the container, because not the operations of every *Stream* will be executed immediately.* Stream* operations can be divided into two categories. One is the **intermediate operations**, and the other is **terminal operations**. Only the terminal operations will lead to real execution of code, while intermediate operations only serve to make some marks to indicate the operations required to the *Stream*. This means that by associating multiple operations on the *Stream*, only one iteration is needed. You should be no stranger to it if you are familiar with Spark RDD.
Concluding remarks
Java 8 introduced *Lambda* expressions, opening the gate to functional programming. Don't get bogged down on this concept if you are not familiar with functional programming. Succinct and clear writing forms in programming and powerful *Stream APIs* will familiarize you with *Lambda* expressions quickly.
This article is only a brief introduction to *Java Lambda* expressions, in a hope to inspire your interests in Java functional programming. That couldn't be better if this article makes you find *Lambda* expressions and functional programming interesting, and want to study them further. I listed some useful reference at the end of this article.
I should thank Alibaba's “Westbound Study Tour Plan” for the support they've given us, and thank Alibaba middleware team for providing funding for us to take part in JaveOne 2016, the top technical conference of Java language in San Francisco, the US. We have the opportunity to conduct face-to-face exchanges with the best minds across the world and get in touch with the latest developments in Java language. Also, I should thank the two teammates who accompanied me in the study tour. Their company and care made my westbound travel so rich and colorful. The surprises during this tour will be deeply rooted in our memories.