Java-30 – Lambda expressions

44 Lambda expressions

44.1 Overview of functional programming ideas

In mathematics, a function is a set of calculation schemes with input and output, that is, “what to do with something”. Relatively speaking, object-oriented places too much emphasis on “things must be done in the form of objects”, while functional thinking tries to ignore the complex syntax of object-oriented – emphasizes what to do, not what form to do .

Object-oriented thinking:

? To do one thing, find an object that can solve the problem, call the method of the object, and complete the thing.

Functional programming ideas:

? As long as the result can be obtained, it doesn’t matter who does it or how it is done. What is important is the result, not the process

44.2 Redundant Runnable code

Traditional writing

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 . code show as below:

public class Demo01Runnable {<!-- -->
public static void main(String[] args) {<!-- -->
    // anonymous inner class
Runnable task = new Runnable() {<!-- -->
@Override
public void run() {<!-- --> // override abstract method
System.out.println("Multi-thread task execution!");
}
};
new Thread(task).start(); // start thread
}
}

Based on the idea of “everything is an object”, this approach is understandable: first create an anonymous inner class object of the Runnable interface to specify the task content, and then hand it over to a thread to start.

Code Analysis

For the anonymous inner class usage of Runnable, several points can be analyzed:

  • The Thread class needs the Runnable interface as a parameter, and the abstract run method is the core used to specify the content of the thread task;
  • In order to specify the method body of run, has to require the implementation class of the Runnable interface;
  • In order to save the trouble of defining a RunnableImpl implementation class, have to use an anonymous inner class;
  • 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 wrongly written;
  • In fact, only the method body seems to be the key.

44.3 Programming thought transformation

What to do, not how to do it

Do we really want to create an anonymous inner class object? No. We have to create an object just to do this. What we really want to do is: pass the code in the run method body to the Thread class to know.

Pass a piece of code – that’s what we’re really here for. And creating an object is only a means that has to be taken due to the limitation of object-oriented syntax. So, is there an easier way? If we return the focus from “how to do” to the essence of “what to do”, we will find that as long as the purpose can be better achieved, the process and form are not important.

Life examples

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-sB6jxGF7-1679378147473)(https://ming-log.oss-cn-hangzhou.aliyuncs.com/img /01-transportation.png)]

When we need to go from Beijing to Shanghai, we can choose high-speed rail, car, cycling or walking. Our real purpose is to reach Shanghai, and the form of how to get there is not important, so we have been exploring whether there is a better way than high-speed rail – by plane.

And now this kind of aircraft (or even a spaceship) has been born: In Java 8 (JDK 1.8) released by Oracle in March 2014, the heavyweight new feature of Lambda expression was added, opening up for us opened the door to a new world.

44.4 Experience the better way of writing Lambda

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

public class Demo02LambdaRunnable {<!-- -->
public static void main(String[] args) {<!-- -->
new Thread(() -> System.out.println("Multi-threaded task execution!")).start(); // Start the thread
}
}

This code is exactly the same as the execution effect just now, and can be passed at a compilation level of 1.8 or higher. 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 “having to create an interface object”, and there is no longer the burden of “abstract method overwriting and rewriting”, it’s that simple!

44.5 Review of anonymous inner classes

How Lambda Beats Object-Orientation In the example above, the core code is really just something like this:

() -> System.out.println("Multi-thread task execution!")

To understand the semantics of Lambda, we need to start with traditional code.

Using the implementation class

To start a thread, you need to create an object of the Thread class and call the start method. In order to specify the content of the thread execution, you need to call the constructor of the Thread class:

  • public Thread(Runnable target)

In order to obtain the implementation object of the Runnable interface, you can define an implementation class RunnableImpl for this interface:

public class RunnableImpl implements Runnable {<!-- -->
@Override
public void run() {<!-- -->
System.out.println("Multi-thread task execution!");
}
}

Then create the object of the implementation class as the construction parameter of the Thread class:

public class Demo03ThreadInitParam {<!-- -->
public static void main(String[] args) {<!-- -->
Runnable task = new RunnableImpl();
new Thread(task).start();
}
}

Using anonymous inner classes

This RunnableImpl class only exists to implement the Runnable interface, and it is only used once, so the syntax of anonymous inner class can save the separate definition of this class , i.e. anonymous inner class:

public class Demo04ThreadNameless {<!-- -->
public static void main(String[] args) {<!-- -->
new Thread(new Runnable() {<!-- -->
@Override
public void run() {<!-- -->
System.out.println("Multi-thread task execution!");
}
}).start();
}
}

Benefits and disadvantages of anonymous inner classes

On the one hand, the anonymous inner class can help us save the definition of the implementation class; on the other hand, the syntax of the anonymous inner class–It is really too complicated!

Semantic Analysis

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

  • public abstract void run();

That is, a scheme to do things (actually a function) has been formulated:

  • No parameters: No conditions are required to execute the scenario.
  • No return value: The proposal did not produce any results.
  • Code block (method body): the specific execution steps of the solution.

The same semantics is reflected in the syntax of Lambda, which is simpler:

() -> System.out.println("Multi-thread task execution!")
  • The previous pair of parentheses are the parameters of the run method (none), which means that no conditions are required;
  • An arrow in the middle represents passing the previous parameters to the following code;
  • The following output statement is the business logic code.

44.6 Lambda standard format

Lambda omits object-oriented rules and regulations, and its format consists of 3 parts:

  • some parameters
  • an arrow
  • a piece of code

The standard format of a Lambda expression is:

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

Format specification:

  • The syntax inside parentheses is the same as for traditional method parameter lists: leave blank if no parameter; multiple parameters are separated by commas.
  • -> is a newly introduced syntax format, representing pointing action.
  • The syntax inside curly braces is basically consistent with the traditional method body requirements.

44.7 Exercise: Use Lambda standard format (no parameter and no return)

Title

Given a cook Cook interface, it contains the only abstract method makeFood, which has no parameters and no return value. as follows:

public interface Cook {<!-- -->
    void makeFood();
}

In the following code, please use Lambda’s standard format to call the invokeCook method to print out the words “It’s time to eat!”:

public class Demo05InvokeCook {<!-- -->
    public static void main(String[] args) {<!-- -->
        // TODO Please use Lambda [standard format] to call the invokeCook method here
    }

    private static void invokeCook(Cook cook) {<!-- -->
        cook. makeFood();
    }
}

Answer

public static void main(String[] args) {<!-- -->
    invokeCook(() -> {<!-- -->
      System.out.println("It's time to eat!");
    });
}

Remarks: The parentheses represent that the parameters of the Cook interface makeFood abstract method are empty, and the curly braces represent the method body of makeFood.

44.8 Lambda parameters and return values

Requirements:
    Use an array to store multiple Person objects
    Use the sort method of Arrays to sort the Person objects in the array in ascending order by age

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 an object array needs to be sorted, the Arrays.sort method needs a Comparator interface instance to specify the sorting rules. Suppose there is a Person class, which contains two member variables String name and int age:

public class Person {<!-- -->
    private String name;
    private int age;
    
    // Omit the constructor, toString method and Getter Setter
}

Traditional writing

If the traditional code is used to sort the Person[] array, the writing method is as follows:

import java.util.Arrays;
import java.util.Comparator;

public class Demo06Comparator {<!-- -->
    public static void main(String[] args) {<!-- -->
      // Originally an array of age disordered objects
        Person[] array = {<!-- -->
        new Person("Guli Nazha", 19),
        new Person("Di Lieba", 18),
       new Person("Malzahar", 20) };

      // anonymous inner class
        Comparator<Person> comp = new Comparator<Person>() {<!-- -->
            @Override
            public int compare(Person o1, Person o2) {<!-- -->
                return o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // The second parameter is the sorting rule, which is the Comparator interface instance

        for (Person person : array) {<!-- -->
            System.out.println(person);
        }
    }
}

This approach seems to be “natural” in object-oriented thinking. The instance of the Comparator interface (using an anonymous inner class) represents the sorting rule of “according to age from youngest to oldest”.

Code Analysis

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

  • For sorting, the Arrays.sort method needs a sorting rule, that is, an instance of the Comparator interface, and the abstract method compare is the key;
  • In order to specify the method body of compare, has to require the implementation class of the Comparator interface;
  • In order to save the trouble of defining a ComparatorImpl implementation class, have to use an anonymous inner class;
  • 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 wrongly written;
  • In fact, only the parameters and method body are critical.

Lambda writing

import java.util.Arrays;

public class Demo07ComparatorLambda {<!-- -->
    public static void main(String[] args) {<!-- -->
        Person[] array = {<!-- -->
          new Person("Guli Nazha", 19),
          new Person("Di Lieba", 18),
          new Person("Malzahar", 20) };

        Arrays.sort(array, (Person a, Person b) -> {<!-- -->
          return a. getAge() - b. getAge();
        });

        for (Person person : array) {<!-- -->
            System.out.println(person);
        }
    }
}

44.9 Exercise: Use Lambda standard format (with parameters and returns)

Title

Given a calculator Calculator interface, which contains the abstract method calc can add two int numbers to get the sum value:

public interface Calculator {<!-- -->
    int calc(int a, int b);
}

In the following code, please use Lambda’s standard format to call the invokeCalc method to complete the addition calculation of 120 and 130:

public class Demo08InvokeCalc {<!-- -->
    public static void main(String[] args) {<!-- -->
        // TODO Please use Lambda [standard format] to call the invokeCalc method here to calculate the result of 120 + 130?
    }

    private static void invokeCalc(int a, int b, Calculator calculator) {<!-- -->
        int result = calculator. calc(a, b);
        System.out.println("The result is: " + result);
    }
}

Answer

public static void main(String[] args) {<!-- -->
    invokeCalc(120, 130, (int a, int b) -> {<!-- -->
      return a + b;
    });
}

Remarks: The parentheses represent the parameters of the calc abstract method of the Calculator interface, and the curly braces represent the method body of calc.

44.10 Lambda omitted format

Can be deduced or omitted

Lambda emphasizes “what to do” rather than “how to do it”, 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:

public static void main(String[] args) {<!-- -->
  invokeCalc(120, 130, (a, b) -> a + b);
}

Omit rules

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

  1. The type of parameters in parentheses can be omitted;
  2. If there is one and only one parameter in the parentheses, the parentheses can be omitted;
  3. If there is one and only one statement inside the curly braces, you can omit the curly 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 multithreading case at the beginning of this chapter accordingly.

44.11 Exercise: Use Lambda to omit format

Title

Still use the Cook interface with the only makeFood abstract method above, in the following code, please use the omitted format of Lambda to call The invokeCook method prints out the words “It’s time to eat!”:

public class Demo09InvokeCook {<!-- -->
    public static void main(String[] args) {<!-- -->
        // TODO Please use Lambda here [omitted format] to call the invokeCook method
    }

    private static void invokeCook(Cook cook) {<!-- -->
        cook. makeFood();
    }
}

Answer

public static void main(String[] args) {<!-- -->
  invokeCook(() -> System.out.println("It's time to eat!"));
}

44.12 Prerequisites for Lambda

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

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

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