A preliminary study on Java Lambda expressions
Foreword
This article was inspired by Trisha Gee’s keynote speech Refactoring to Java 8 at JavaOne 2016.
Java 8 has been released for more than two years, but many people are still using JDK7. For enterprises, being cautious in technology is not necessarily a bad thing, but for personal learning, if you do not learn new technologies, you are likely to be abandoned by them. An important change in Java 8 is the introduction of Lambda expression, which sounds awesome. Even though I don’t know what Lambda expression is, I still feel that it is awesome. Don't be afraid, at the language level, Lambda expression is just a new syntax. With it, Java will open the door to functional programming.
Why do you need Lambda expressions
Don’t worry about what is Lambda expression and what is functional programming. Let’s first take a look at the conveniences brought by the new syntax features of Java 8. I believe you will remember them.
Before there is a Lambda expression, to create a new thread, you need to write like this:
new Thread(new Runnable(){ @Override public void run(){ System.out.println("Thread run()"); } }).start();
After there is a Lambda expression, you can write like this:
new Thread( () -> System.out.println("Thread run()") ).start();
As you can see, the previously useless template code is gone! As shown above, a common use of Lambda expressions is to replace (some) anonymous inner classes, but the role of Lambda expressions is not limited to this.
Principle of Lambda Expression
If you are new to Lambda expression, you may think it is magical: you can directly define a function without declaring the name of the class or method. But in fact, this is just a little trick provided by the compiler, and the principle behind it is not difficult to understand. The following are several possible writing forms of Lambda expressions:
Runnable run = () -> System.out.println("Hello World");// 1ActionListener listener = event -> System.out.println("button clicked");// 2Runnable multiLine = () -> {// 3 System.out.println("Hello "); System.out.println("World"); }; BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5
You can find from the above example:
Lambda expressions have types, and the left side of the assignment operation is the type. The type of the lambda expression is actually the type of the corresponding interface.
Lambda expressions can contain multiple lines of code, and you need to use curly brackets to enclose the code block, just like writing a function body.
Most of the time, the type can be omitted from the parameter list of a lambda expression, as in Listings 2 and 5. This is due to javac's type derivation mechanism, the compiler can deduce type information based on context.
In fact, each Lambda expression is the abbreviation of the original anonymous inner class, which implements a functional interface (Functional Interface). The so-called functional interface refers to an interface with the @FunctionalInterface annotation added and only one interface function inside. Java is a strongly typed language. Regardless of whether it is explicitly specified, each variable and object must have a clear type. When not explicitly specified, the compiler will try to determine the type. The type of the lambda expression is the type of the corresponding function interface.
Lambda expressions and Stream
Another important use of Lambda expressions is to use them with Stream. Stream is a sequence of elements supporting sequential and parallel aggregate operations. Stream is a sequence of elements that supports various operations on these elements, and these operations are specified through Lambda expressions. You can think of a Stream as a view of a Java Collection, just like an iterator is a view of a container (but the Stream does not modify the contents of the container). The following examples show common uses of Stream.
Example 1
Suppose you need to select a string starting with a number from a string list and output it. Before Java 7, you needed to write like this:
List<String> list = Arrays.asList("1one", "two", "three", "4four");for(String str : list){ if(Character.isDigit(str.charAt(0))){ System.out.println(str); } }
And in Java 8, you can write like this:
List<String> list = Arrays.asList("1one", "two", "three", "4four"); list.stream()// 1.得到容器的Steam .filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串 .forEach(str -> System.out.println(str));// 3.输出字符串
The above code first 1 . Call the List.stream() method to get the Stream of the container, 2. Then call the filter() method to filter out the string starting with a number, 3. Finally, call the forEach() method to output the result.
Using Stream has two obvious benefits:
The template code is reduced, only Lambda expressions are used to specify the required operations, and the code semantics are clearer and easier to read.
Change the external iteration to the internal iteration of Stream, which facilitates the JVM itself to optimize the iteration process (for example, it can be iterated in parallel).
Example 2
Suppose you need to select all strings that do not start with a number from a list of strings, convert them to uppercase, and put the results into a new collection. The code written in Java 8 is as follows:
List<String> list = Arrays.asList("1one", "two", "three", "4four"); Set<String> newList = list.stream()// 1.得到容器的Stream .filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串 .map(String::toUpperCase)// 3.转换成大写形式 .collect(Collectors.toSet());// 4.生成结果集
上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法选出不以数字开头的字符串,3. 之后调用map()方法将字符串转换成大写形式,4. 最后调用collect()方法将结果转换成Set。这个例子还向我们展示了方法引用(method references,代码中标号3处)以及收集器(Collector,代码中标号4处)的用法,这里不再展开说明。
通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations),另一类是结束操作(terminal operation),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。
结语
Java 8引入Lambda表达式,从此打开了函数式编程的大门。如果你之前不了解函数式编程,不必纠结于这个概念。编程过程中简洁明了的书写形式以及强大的Stream API会让你很快熟悉Lambda表达式的。
本文只对Java Lambda表达式的基本介绍,希望能够激发读者对Java函数式编程的兴趣。如果本文能够让你觉得Lambda表达式很好玩,函数式编程很有趣,并产生了进一步学习的欲望,那就再好不过了。文末参考文献中列出了一些有用的资源。
参考文献
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk
《Java 8函数式编程 [英]沃伯顿》
https://www.oracle.com/javaone/speakers.html#gee