Explain Lambda expressions in detail

Explain Lambda expressions in detail

Introduction:

An overview of the idea of ​​functional programming: In mathematics, a function is a set of calculation schemes with input and output, that is, "do something with something". Relatively speaking, object-oriented overemphasizes "things must be done in the form of objects", while functional thinking tries to ignore the complex syntax of object-oriented—emphasizing what to do, not what form to do. What to do, not how to do it, do we really want to create an anonymous inner class object? Do not. We just have to create an object to do this.

What we really want to do is: pass the code in the run method to the Thread class to know.

Pass a piece of code-this is our real purpose. The creation of objects is just a method that has to be adopted due to the limitation of object-oriented syntax. So, is there an easier way? If we return the focus from "how to" to the essence of "what to do," we will find that as long as we can better achieve the goal, the process and the form are actually not important.

  • Understanding Lambda optimization

When a thread needs to be started to complete a task, the task content is usually defined through the java.lang.Runnable interface, and the java.lang.Thread class is used to start the thread.

The traditional way of writing, the code is as follows:

 1/**
 2 * @Author: Auser·Jie
 3 * @DATE: 2019/11/4 21:46
 4 */
 5public class Demo {
 6 public static void main(String[] args) {
 7 new Thread(new Runnable() {
 8 @Override
 9 public void run() {
10 System.out.println("Multi-threaded task execution!");
11}
12 }).start();
13}
14}

In the spirit of "everything is an object", this approach is understandable: first create an anonymous inner class object of Runnable interface to specify the task content, and then hand it over to a thread to start.

Code analysis:

  1. For the use of Runnable's anonymous inner class, several points can be analyzed:
  2. The Thread class requires the Runnable interface as a parameter, and the abstract run method is used to specify the core of the thread task content;
  3. In order to specify the method body of run, the implementation class of the Runnable interface has to be required;
  4. In order to save the trouble of defining a RunnableImpl implementation class, anonymous inner classes have to be used;
  5. The abstract run method must be overridden, so the method name, method parameters, and method return value have to be written again and cannot be written wrongly;
  6. In fact, it seems that only the method body is the key.
  • Lambda expression writing,

code show as below:

With the new syntax of Java 8, the anonymous inner class writing of the above Runnable interface can be equivalent through simpler Lambda expressions:

1/**
2 * @Author: Auser·Jie
3 * @DATE: 2019/11/4 21:50
4 */
5public class Demo02LambdaRunnable {
6 public static void main(String[] args) {//start thread
7 new Thread(() -> System.out.println("Multi-threaded task execution!")).start(); 
8 }
9}

This code is exactly the same as the execution effect just now, and it can be passed under the 1.8 or higher compilation level. It can be seen from the semantics of the code: we start a thread, and the content of the thread task is specified in a more concise form. There is no longer the constraint of "have to create interface objects", and the burden of "abstract method overwriting and rewriting", it's that simple!

  • Lambda format

standard format:

Lambda eliminates the object-oriented rules and regulations, and the format consists of 3 parts:

  1. Some parameters
  2. An arrow
  3. A piece of code

The standard format of lambda expression is:

1(parameter type parameter name) -> {code statement}

Format description:

  • The syntax in parentheses is consistent with the traditional method parameter list: if there is no parameter, leave it blank; multiple parameters are separated by commas.
  • -> is a newly introduced grammatical format, which stands for pointing actions.
  • The syntax in the braces is basically consistent with the requirements of the traditional method.

Anonymous inner class compared with lambda:

 1/**
 2 * @Author: Auser·Jie
 3 * @DATE: 2019/11/4 21:58
 4 */
 5new Thread(new Runnable() {
 6@Override
 7public void run() {
 8 System.out.println("Multi-threaded task execution!");
 9 }
10 }).start()

Carefully analyze the code, the Runnable interface has only one definition of run method:

  • public abstract void run(); That is, a plan to do things (in fact, a method) is developed:
  1. No parameters: The program can be executed without any conditions.
  2. No return value: The program does not produce any results.
  3. Code block (method body): the specific execution steps of the program.
  • The same semantics is reflected in Lambda syntax, which is simpler:
1() -> System.out.println("Multi-threaded task execution!")
  1. The previous pair of parentheses are the parameters of the run method (none), which means that no conditions are required;
  2. An arrow in the middle represents passing the previous parameter to the following code;
  3. The following output statement is the business logic code.
  • Lambda parameters and returns

The following example demonstrates the usage scenario code of the java.util.Comparator interface, where the abstract method is defined as:

  • public abstract int compare(T o1, T o2);

When you need to sort an array of objects, the Arrays.sort method requires an instance of the Comparator interface to specify the sorting rules. Suppose there is a Person class with two member variables: String name and int age:

1public class Person {
2 private String name;
3 private int age;
4
5//Omit the constructor, toString method and Getter Setter
6}

Traditional way of writing If you use traditional code to sort the Person[] array, the way of writing is as follows:

 1/**
 2 * @Author: Auser·Jie
 3 * @DATE: 2019/11/4 22:35
 4 */
 5public class TestComparator {
 6 public static void main(String[] args) {
 7//Object array with out-of-order age
 8 Person[] array = {new Person("墨白", 19),
 9 new Person("Little lemon is not sour", 18),
10 new Person("大白", 20) };
11//Anonymous inner class
12 Comparator<Person> comp = new Comparator<Person>() {
13 @Override
14 public int compare(Person o1, Person o2) {
15 return o1.getAge()-o2.getAge();
16}
17 };
18 Arrays.sort(array, comp);
19//The second parameter is the sorting rule, that is, the Comparator interface instance
20 for (Person person: array) {
21 System.out.println(person);
22 }
23 }
24}

This approach seems to be "taken for granted" in object-oriented thinking. Among them, the instance of the Comparator interface (using the anonymous inner class) represents the sorting rule of "from youngest to oldest".

Code analysis:

Let's figure out what the above code really does.

  1. In order to sort, the Arrays.sort method needs a sorting rule, that is, an instance of the Comparator interface, and the abstract method compare is the key;
  2. In order to specify the method body of compare, the implementation class of the Comparator interface has to be required;
  3. In order to save the trouble of defining a ComparatorImpl implementation class, anonymous inner classes have to be used;
  4. The abstract compare method must be overridden, so the method name, method parameters, and method return value have to be written again and cannot be written wrongly;
  5. In fact, only the parameters and method body are the key.

Lambda writing

 1/**
 2 * @Author: Auser·Jie
 3 * @DATE: 2019/11/4 21:50
 4 */
 5public class TestComparatorLambda {
 6 public static void main(String[] args) {
 7 Person[] array = {new Person("墨白", 19),
 8 new Person("Little lemon is not sour", 18),
 9 new Person("大白", 20) };
10 Arrays.sort(array, (Person a, Person b) -> {
11 return a.getAge()-b.getAge();
12 });
13 for (Person person: array) {
14 System.out.println(person);
15}
16}
17}

Omission rule

Based on the Lambda standard format, the rules for using omission are:

1. The type of the parameter in the parentheses can be omitted;

2. If there is only one parameter in the parentheses, the parentheses can be omitted;

3. If there is one and only one statement in the braces, you can omit the braces, the return keyword, and the statement semicolon regardless of whether there is a return value.

Note: After mastering these omission rules, please review the multi-threaded case at the beginning of this article accordingly

  • Can be deduced or omitted

Lambda emphasizes "what" rather than "how", so any information that can be derived from the context can be omitted. For example, the above example can also use the omission of Lambda:

1Runnable interface simplified:
21. () -> System.out.println("Multi-threaded task execution!")
3Comparator interface simplified:
42. Arrays.sort(array, (a, b) -> a.getAge()-b.getAge());
  • The premise of Lambda

Lambda's syntax is very concise, without the constraints of object-oriented complexity. However, there are several problems that need special attention when using:

  1. Lambda must have an interface, and there is only one abstract method in the interface. Whether it is the built-in Runnable and Comparator interface of the JDK or a custom interface, Lambda can only be used when the abstract method in the interface exists and is unique.
  2. The use of Lambda must have contextual inference. That is, the parameter or local variable type of the method must be the interface type corresponding to Lambda in order to use Lambda as an instance of the interface.

Note: An interface with one and only one abstract method is called a "functional interface".