Why use Lambda expressions
Let's first look at a few pieces of code that Java 8 used to encounter:
Create thread and start
// Create thread public class Worker implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { doWork(); } } } // Start thread Worker w = new Worker(); new Thread(w).start(); Copy code
Compare arrays
// Define a comparator public class LengthComparator implements Comparator<String> { @Override public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); } } //Compare character arrays Arrays.sort(words, new LengthComparator()); Copy code
Add a click event to the button
public void onClick(Button button) { button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("button clicked."); } }); } Copy code
We are used to these three pieces of code.
But their problem is also very prominent: there is too much noise! To realize the comparison function of an array, we need to write at least 5 lines of code, but only one line of code is what we really care about!
Java's complex and redundant code implementation has been criticized by programmers. Fortunately, with the rise of JVM platform language Scala and the popularity of functional programming style, Oracle has made revolutionary changes in the eighth series of Java versions, and introduced a series of syntax features of functional programming style, such as Lambda expression and Stream.
If you use Lambda expressions, the implementation of the above three pieces of code will become extremely concise.
Create thread and start (Lambda version)
new Thread(() -> { for (int i = 0; i < 100; i++) { doWork(); } }).start(); Copy code
Comparison array (Lambda version)
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length()) Copy code
Add a click event to the button (Lambda version)
button.addActionListener((event) -> System.out.println("button clicked.")); Copy code
What about? Through Lambda expressions, the code has become concise enough for you to focus on business code.
Syntax of Lambda expressions
Format: (parameter) - > expression
Of which:
- Parameters can be 0-n. If there are multiple parameters, separate them with commas (,). If there is a parameter, the bracket () can be omitted; If there are no parameters, parentheses () cannot be omitted. [this is not pure enough. It's still a little worse than scala!], The type name can be added before the parameter, but it can be omitted due to the automatic type derivation function.
- An expression can be a single line expression or multiple statements. If there are multiple statements, they need to be wrapped in braces {}.
- The expression does not need to display the result returned by execution, it will be automatically deduced from the context. Here are some examples:
One parameter
event -> System.out.println("button clicked.") Copy code
Multiple parameters
(first, second) -> Integer.compare(first.length(), second.length() Copy code
0 parameters
() -> System.out.println("what are you nongshalei?") Copy code
Expression block
() -> {for (int i = 0; i < 100; i++) { doWork();}} Copy code
Functional interface
A new annotation has been added in Java 8: @FunctionalInterface , functional interface.
What is a functional interface? It includes the following features:
- There is only one abstract method in the interface, but default and static methods are allowed.
- @FunctionalInterface Annotations are not required, but it is recommended to add them, so that the compiler can check whether there is only one abstract method in the interface.
The essence of Lambda expression is the anonymous implementation of functional interface. Only the original interface implementation is expressed in a syntax more like functional programming.
Java. Net for Java 8 util. A large number of functional interfaces have been built in the function package, as shown below:
Functional interface | Parameter type | Return type | Method name | describe |
---|---|---|---|---|
Supplier | nothing | T | get | Generate a data of type T |
Consumer | T | void | accept | Consume a data of type T |
BiConsumer<T,U> | T,U | void | accept | Consumption type T and type U data |
Function<T,R> | T | R | apply | The data of parameter type T is converted into data of type R through function processing |
BiFunction<T,U,R> | T,U | R | apply | The data with parameter types T and U are transformed into data of type R through function processing |
UnaryOperator | T | T | apply | A unary operation on type t still returns type T |
BinaryOperator | T,T | T | apply | Binary operation on type t still returns type T |
Predicate | T | void | test | Performs function processing on type T and returns a Boolean value |
BiPredicate<T,U> | T,U | void | test | Function on types T and U and return Boolean values |
It can be seen that:
- The built-in functional interfaces are mainly divided into four categories: Supplier, Consumer, Function and Predicate. Operator is a special case of Function.
- Except that the Supplier does not provide binary parameters (which is related to the fact that java does not support multiple return values), the other three classes provide binary input parameters.
The following is a comprehensive example:
public class FunctionalCase { public static void main(String[] args) { String words = "Hello, World"; String lowerWords = changeWords(words, String::toLowerCase); System.out.println(lowerWords); String upperWords = changeWords(words, String::toUpperCase); System.out.println(upperWords); int count = wordsToInt(words, String::length); System.out.println(count); isSatisfy(words, w -> w.contains("hello")); String otherWords = appendWords(words, ()->{ List<String> allWords = Arrays.asList("+abc", "->efg"); return allWords.get(new Random().nextInt(2)); }); System.out.println(otherWords); consumeWords(words, w -> System.out.println(w.split(",")[0])); } public static String changeWords(String words, UnaryOperator<String> func) { return func.apply(words); } public static int wordsToInt(String words, Function<String, Integer> func) { return func.apply(words); } public static void isSatisfy(String words, Predicate<String> func) { if (func.test(words)) { System.out.println("test pass"); } else { System.out.println("test failed."); } } public static String appendWords(String words, Supplier<String> func) { return words + func.get(); } public static void consumeWords(String words, Consumer<String> func) { func.accept(words); } } Copy code
If you think these built-in functional interfaces are not enough, you can also customize your own functional interfaces to meet more needs.
Method reference
If the Lambda expression already has an implemented method, it can be simplified with a method reference. The syntax referenced by the method is as follows:
- Object:: instance method
- Class:: static method
- Class:: instance method
Thus, the Lambda expression mentioned earlier:
event -> System.out.println(event) Copy code
Can be replaced by:
System.out::println Copy code
Another example:
(x,y)->x.compareToIgnoreCase(y) Copy code
Can be replaced by:
String::compareToIgnoreCase Copy code
Note: the method name cannot be followed by parameters! It can be written as system Out:: println, but cannot be written as system out::println(“hello”)
If you can get the this parameter of this instance, you can directly access it with the this:: instance method. For the method specified by the parent class, you can access it with the super:: instance method.
Here is an example:
public class Greeter { public void greet() { String lowcaseStr = changeWords("Hello,World", this::lowercase); System.out.println(lowcaseStr); } public String lowercase(String word) { return word.toLowerCase(); } public String changeWords(String words, UnaryOperator<String> func) { return func.apply(words); } } class ConcurrentGreeter extends Greeter { public void greet() { Thread thread = new Thread(super::greet); thread.start(); } public static void main(String[] args) { new ConcurrentGreeter().greet(); } } Copy code
Constructor reference
Constructor references are similar to method references, except that the function interface returns an instance object or array. The constructor references the following syntax:
- Class:: new
- Array:: new
for instance:
List<String> labels = Arrays.asList("button1", "button2"); Stream<Button> stream = labels.stream().map(Button::new); List<Button> buttons = stream.collect(Collectors.toList()); Copy code
Labels stream(). Map (button:: new) is equivalent to labels stream(). map(label->new Button(label))
Take another example of constructor reference of array type:
Button[] buttons = stream.toArray(Button[]::new); Copy code
The Stream is directly converted into an array type. Here, the array type is marked with button []: new.
Variable scope
Let's start with a code:
public void repeatMsg(String text, int count) { Runnable r = () -> { for (int i = 0; i < count; i++) { System.out.println(text); Thread.yield(); } }; } Copy code
A lambda expression generally consists of the following three parts:
- parameter
- expression
- Free variable
Parameters and expressions are easy to understand. What is the free variable? It is the external variables referenced in the lambda expression, such as the text and count variables in the above example.
Students who are familiar with functional programming will find that Lambda expressions are actually "closures". It's just that Java 8 doesn't have that name. For free variables, if Lambda expressions need to be referenced, modification is not allowed.
In fact, in the anonymous inner class of Java, if you want to reference an external variable, the variable needs to be declared final. Although the free variable of Lambda expression does not have to be declared final, it is also not allowed to be modified.
For example, the following code:
public void repeatMsg(String text, int count) { Runnable r = () -> { while (count > 0) { count--; // Error, unable to modify the value of external variable System.out.println(text); } }; } Copy code
In addition, it is not allowed to declare a parameter or local variable with the same name as a local variable in a Lambda expression. For example, the following code:
Path first = Paths.get("/usr/bin"); Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length()); // Error, variable first has been defined Copy code
Default method in interface
Let's talk about why we should add a default method to the Java 8 interface.
For example, the designer of the Collection interface has added a forEach() method to traverse the Collection more concisely. For example:
list.forEach(System.out::println()); Copy code
However, if a new method is added to the interface, according to the traditional method, the custom implementation classes of the Collection interface must implement the forEach() method, which is unacceptable to the majority of existing implementations.
So the designers of Java 8 came up with this method: add a new method type in the interface, called the default method, which can provide the default method implementation. In this way, if the implementation class does not implement the method, it can use the implementation in the default method by default.
An example:
public interface Person { long getId(); default String getName() { return "jack"; } } Copy code
The addition of default methods can replace the previous classic design methods of interfaces and abstract classes, and uniformly define abstract methods and default implementations in one interface. This is probably a skill stolen from Scala's trail.
Static methods in interfaces
In addition to the default methods, Java 8 also supports the definition of static methods and implementations in interfaces.
For example, before Java 8, for the Path interface, a Path tool class is generally defined to implement the auxiliary methods of the interface through static methods.
It's easy to have static methods in the interface. It's done in one interface! Although this seems to destroy the original design idea of the interface.
public interface Path{ public static Path get(String first, String... more) { return FileSystem.getDefault().getPath(first, more); } } Copy code
So the Paths class is meaningless~
Summary
The use of Lambda expressions can greatly reduce the redundant template code and make you pay more attention to business logic rather than copying a pile of duplicate code, unless you are in a company that measures the workload by the number of code lines. What do you think?