Lambda expression
1. Introduction
Before Java 8, if we wanted to pass some functions to some methods, we always had to write anonymous classes. In the past, the writing method of registered event listener is very similar to the following example code:
manager.addScheduleListener(new ScheduleListener() { @Override public void onSchedule(ScheduleEvent e) { // Event listener implementation goes here... } });
Here, we have added some custom code to the Schedule listener. We need to define an anonymous inner class first, and then pass some functions to the onSchedule method.
It is the limitation of Java in passing ordinary methods or functions as parameters that Java 8 adds a new language level function called Lambda expression.
2. Why does Java need Lambda expressions
In java, we can easily assign a value to a variable, such as int a =0;int b=a; But how do we assign a piece of code and a function to a variable? What type should this variable be?
Java is an object-oriented language. Except for the original data types, all content in Java is an object. In functional languages, we only need to assign variables to functions and pass this function as parameters to other functions to realize specific functions. JavaScript is a paradigm for functional programming languages (closures).
With the addition of Lambda expressions, Java has the ability of functional programming. In other languages, the type of Lambda expression is a function; But in Java, Lambda expressions are represented as objects, so they must be bound to a specific object type called a functional interface.
Lambda allows you to simply pass a behavior or code. Lambda can be regarded as an anonymous function and a method without a declared name, but like an anonymous class, it can also be passed to a method as a parameter.
3. Structure of Lambda expression
Lambda expression is an anonymous function (not very accurate for Java, but we don't worry about it here). Simply put, this is an undeclared method, that is, there is no access modifier, return value declaration and name.
It is particularly useful where methods are used only once, and the method definition is very short. It saves us work such as including class declarations and writing individual methods.
Lambda expressions in Java usually use the syntax of (argument) - > (body), such as:
The basic structure of Lambda expression is:
- Left: Specifies the parameter list required by Lambda expression
- Right: Specifies the Lambda body, which is the implementation logic of the abstract method, that is, the function to be performed by the Lambda expression.
(arg1, arg2...) -> { body } (type1 arg1, type2 arg2...) -> { body }
3.1. Lambda expression: Syntax
Structure of Lambda expression:
- Lambda expressions can have zero, one or more parameters.
- You can explicitly declare the type of a parameter, or the compiler can automatically infer the type of a parameter from the context. For example, (int a) is the same as just (a).
- Parameters are enclosed in parentheses and separated by commas. For example, (a, b) or (int a, int b) or (String a, int b, float c). Empty parentheses are used to represent a set of empty parameters. For example () - > 42.
- When there is one and only one parameter, parentheses are not necessary if the type is not explicitly specified. For example, a - > return a * a. The body of a Lambda expression can contain zero or one or more statements.
- If the body of a Lambda expression has only one statement, curly braces need not be written, and the return value type of the expression should be the same as that of an anonymous function.
- If there is more than one statement in the body of a Lambda expression, it must be contained in braces (code block), and the return value type of the expression must be the same as that of an anonymous function.
4. Where to use lambda?
4.1 functional interface
A functional interface is an interface that defines only one abstract method.
- Methods in object are not included
It should be noted that any Object in Java comes from the methods in Object, and all interfaces will naturally inherit from the methods in Object. However, when judging whether it is a functional interface, the methods in Object should be excluded. The following are some examples:
//This is a functional interface interface eat { void eatFood(); } //This is not a functional interface interface eat { default void eatFood() { System.out.println("hello"); }; } //This is a functional interface interface eat { void eatFood(); String toString(); }
Lambda expression allows you to directly provide the implementation of abstract methods of functional interfaces in the form of inline, and take the whole expression as an instance of functional interfaces. Specifically, lambda expressions are functions and instances of specific implementations of interfaces.
You can do the same thing through anonymous inner classes, but it's clumsy: you need to provide an implementation and instantiate its implementation directly inline. As shown in the following example, you can clearly see the simplicity of lambda:
public class TestLambdaAndAnonymity { /** * Execution method * @param r */ public static void process(Runnable r){ r.run(); } public static void main(String[] args) { // Use anonymous classes Runnable r1 = new Runnable() { @Override public void run() { System.out.println("this is r1"); } }; // Using Lambda Runnable r2 = ()-> System.out.println("this is r2"); process(r1); process(r2); // Pass lambda directly as a parameter process(()-> System.out.println("this is r3")); } }
4.2. Function descriptor?
The signature of abstract methods of functional interfaces is basically the signature of Lambda expressions. We call this abstract method function descriptor.
for instance:
The Runnable interface can be regarded as the signature of a function that accepts nothing and returns nothing (void), because it only has an abstract method called run, which accepts nothing and returns nothing (void).
@FunctionalInterface public interface Runnable { public abstract void run(); }
As an example in the previous section: "() - > system. Out. Println (" this is R3 ")", () - > void is the signature of this function, which represents the function whose parameter list is empty and returns void. This is what the Runnable interface represents.
As another example, (apple, Apple) - > int represents a function that accepts two apples as parameters and returns int.
4.3 what about @ functional interface?
If you look at the new Java API, you will find that the functional interface is marked with @ functional interface. This annotation is used to indicate that the interface will be designed as a functional interface. If you define an interface with @ functional interface, but it is not a functional interface, the compiler will return an error indicating the reason. As follows:
@FunctionalInterface private interface ITest{ void test(); void test1(); }
Indicates that there are multiple abstract methods for this interface.
@Functional interface is not required, but it is a good practice for interfaces designed for this purpose.
4.3. Simple use
Next we write a simple lambda code to find the specified ID number and print it out.
Final call:
For the above code implementation, we didn't know the implementation method of findName before calling the extractor method, until we finally passed a method as a parameter into the extractor method.
Reflection: can the functional interface NameCheckInterface be used to represent a lambda expression with two formal parameters (passager and String types) whose return value is bool?
If we cooperate with generics, do we only need to define a general functional interface? Let's rewrite the code:
public class SocketTest { public static void main(String[]args)throws Exception{ List<Passager> passagerList = new ArrayList<>(); passagerList.add(new Passager("Li Si", "123456789")); passagerList.add(new Passager("Zhang San", "123456789")); passagerList.add(new Passager("WangTwo ", "123456789")); excutor(passagerList,(p,str)->p.getPassagerName().equals(str),str-> System.out.println(str)); } private static void excutor(List<Passager> passagerList, NameCheckInterface<Boolean,Passager,String> checker, PrintInterface<String> printer) { for (Passager p : passagerList) { if (checker.findName(p,"Li Si")){ printer.printName(p.getPassagerNo()); } } } } class Passager{ public String name; String passagerNo; public Passager(String name,String passagerNo){ this.name=name; this.passagerNo=passagerNo; } public String getPassagerNo(){ return passagerNo; } public String getPassagerName(){ return name; } } interface NameCheckInterface<T,T1,T2> { T findName(T1 passager,T2 name); } @FunctionalInterface interface PrintInterface<T> { void printName(T name); }
5. Use of Lambda expressions
5.1. Use functional interface
A functional interface defines and defines only one abstract method. Functional interfaces are useful because the signature of abstract methods can describe the signature of Lambda expressions. The signature of an abstract method of a functional interface is called a function descriptor. Therefore, in order to apply different Lambda expressions, you need a set of functional interfaces that can describe common function descriptors.
The library designer of Java 8 has helped you introduce several new functional interfaces into the java.util.function package.
Runnable r = () -> System.out.printf("say hello");//No input parameters, no output Supplier<String> sp = () -> "hello";//Only output messages, no input parameters Consumer<String> cp = r -> System.out.printf(r);//There is an input parameter and no output Function<Integer, String> func = r -> String.valueOf(r);//There is one input parameter and one output parameter BiFunction<Integer, Integer, String> biFunc = (a, b) -> String.valueOf(a + b);//There are two input parameters and one output parameter BiConsumer<Integer, Integer> biCp = (a, b) -> System.out.printf(String.valueOf(a + b));//There are two input parameters and no output parameters
PS: the above is the basic method, and others are based on these extensions
If the above code uses the functional interface in the jdk, there is no need to define additional NameCheckInterface and PrintInterface interfaces. According to the above parameters and the form of the return value, you can directly override the excutor method with BiFunction and Consumer:
private void excutor(List<passager> passagerList, BiFunction<passager,String,Boolean> checker, Consumer<String> printer) { for (passager p : passagerList) { if (checker.apply(p,"Li Si")){ printer.accept(p.getPassagerNo()); } } }
5.2,Consumer
Java. Util. Function. Consumer < T > defines an abstract method called accept, which accepts the object of generic t without returning (void). If you need to access an object of type T and perform some operations on it, you can use this interface.
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t);
As an example, print each value separately:
public class TestConsumer { public static void main(String[] args) { forEach(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i)); } public static <T> void forEach(List<T> list, Consumer<T> c) { for (T t : list) { c.accept(t); } } }
5.3,Function
The Java. Util. Function. Function < T, R > interface defines a method called apply, which accepts an object of generic T and returns an object of generic R. If you need to define a Lambda to map the information of the input object to the output, you can use this interface.
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t);
Map the length of the String list to the Integer list as follows:
public static <T, R> List<R> map(List<T> list, Function<T, R> function) { List<R> results = new ArrayList<>(); for (T t : list) { results.add(function.apply(t)); } return results; } public static void main(String[] args) { List<Integer> map = map( Arrays.asList("Tom", "Jerry", "XiaoMing"), (String s) -> s.length() ); System.out.println(map); }
6. Method reference
6.1 introduction to method reference
Method references are shortcuts to some Lambdas. Allows you to reuse existing method definitions and pass them like Lambda.
When you need to use a method reference, the target reference is placed before the separator:: and the method name is placed after it. For example:
Apple::getWeight
Apple::getWeight refers to the method getWeight defined in the apple class. The method reference is the shortcut of Lambda expression (apple a) - > a.getWeight().
6.2. Construction method reference
There are three main types of method references:
1) Method reference to static method
As shown below, point to the static method parseInt.
//Point to any instance type method Function<String, Integer> function = Integer::parseInt; Integer apply = function.apply("100"); System.out.println(apply); //lambda comparison Function<String, Integer> function = s -> parseInt(s); Integer apply = null; try { apply = function.apply("100"); } catch (Exception e) { e.printStackTrace(); } System.out.println(apply);
2) A method reference to an instance method of any type
As shown below, point to the length method of String
//Point to any instance type method Function<String,Integer> function1 = String::length; Integer hello_world = function1.apply("hello world"); System.out.println(hello_world); //lambda comparison Function<String,Integer> function1 = s -> s.length(); Integer hello_world = null; try { hello_world = function1.apply("hello world"); } catch (Exception e) { e.printStackTrace(); } System.out.println(hello_world);
You are referring to the method of an object, and the object itself is a parameter of Lambda.
3) Method reference to the method of an existing instance object
As follows:
@Data static class Student{ private String name; private Integer age; public Student(String name, Integer age) { this.name = name; this.age = age; } } //Method to point to an existing object Student student = new Student("Jack",10); Supplier<String> supplier = student::getName; String s = supplier.get(); System.out.println(s);
Different from the second article, the object referenced by this method is an external object that already exists, such as the student in the above example.
6.3 constructor reference
For an existing constructor, you can use its name and keyword new to create a reference to it: ClassName::new. Its function is similar to that of a reference to a static method.
There are the following entity classes:
@Data static class Student { private String name; private Integer age; private String address; public Student() { } public Student(String name) { this.name = name; } public Student(String name, Integer age) { this.name = name; this.age = age; } public Student(String name, Integer age, String address) { this.name = name; this.age = age; this.address = address; } }
example:
/** * description: Customize the interface of three parameters * @return: * @author: weirx * @time: 2021/10/19 18:03 */ interface ThreeParamsFunction<T, U, P, R> { R apply(T t, U u, P p); } public static void main(String[] args) { // No construction parameter () - > New student() is converted to Student::new Supplier<Student> supplier = Student::new; Student student = supplier.get(); // A construction parameter (name) - > new student (name) is converted to Student::new Function<String, Student> function = Student::new; function.apply("JACK"); // Two construction parameters (name, age) - > new student (name, age) are converted to Student::new BiFunction<String, Integer, Student> biFunction = Student::new; biFunction.apply("JACK", 10); // Three construction parameters. If three construction parameters are not provided, you need to write an interface yourself // (name, age, address) - > convert new student (name, age, address) to Student::new ThreeParamsFunction<String, Integer, String, Student> threeParamsFunction = Student::new; threeParamsFunction.apply("JACK", 10, "Haerbin"); }
7. Case summary
Sort apples using the sort method of List:
void sort(Comparator<? super E> c)
According to the above sort method, we need a Comparator object to compare apples. Before java8, we need to do this to pass the code:
7.1 transmission code
package test2; import java.util.Arrays; import java.util.Comparator; import java.util.List; public class LambdaTest { public static class Apple { public static void main(String[] args) { List<Apple> apples = Arrays.asList( new Apple(10), new Apple(19), new Apple(9), new Apple(22) ); apples.sort(new CompareApple()); for( Apple apple : apples) { System.out.println(apple.getWeight()); } } private Integer weight; public Apple(Integer weight) { this.weight = weight; } public Integer getWeight() { return weight; } } public static class CompareApple implements Comparator<Apple> { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } } }
output
No, java8: 9 No, java8: 10 No, java8: 19 No, java8: 22
7.2 anonymous classes
package test2; import java.util.Arrays; import java.util.Comparator; import java.util.List; public class LambdaTest { public static class Apple { public static void main(String[] args) { List<Apple> apples = Arrays.asList( new Apple(10), new Apple(19), new Apple(9), new Apple(22) ); apples.sort(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } }); for( Apple apple : apples) { System.out.println("Anonymous class: " + apple.getWeight()); } } private Integer weight; public Apple(Integer weight) { this.weight = weight; } public Integer getWeight() { return weight; } } }
Output:
Anonymous class: 9 Anonymous class: 10 Anonymous class: 19 Anonymous class: 22
7.3 lambda expression
package test2; import java.util.Arrays; import java.util.Comparator; import java.util.List; public class LambdaTest { public static class Apple { public static void main(String[] args) { List<Apple> apples = Arrays.asList( new Apple(10), new Apple(19), new Apple(9), new Apple(22) ); apples.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight())); for( Apple apple : apples) { System.out.println("lambda expression: " + apple.getWeight()); } } private Integer weight; public Apple(Integer weight) { this.weight = weight; } public Integer getWeight() { return weight; } } }
Output:
lambda expression: 9 lambda expression: 10 lambda expression: 19 lambda expression: 22
7.4 method reference
package test2; import java.util.Arrays; import java.util.Comparator; import java.util.List; public class LambdaTest { public static class Apple { public static void main(String[] args) { List<Apple> apples = Arrays.asList( new Apple(10), new Apple(19), new Apple(9), new Apple(22) ); apples.sort(Comparator.comparing(Apple::getWeight)); for( Apple apple : apples) { System.out.println("Method reference: " + apple.getWeight()); } } private Integer weight; public Apple(Integer weight) { this.weight = weight; } public Integer getWeight() { return weight; } } }
output
Method reference: 9 Method reference: 10 Method reference: 19 Method reference: 22
reference resources
1,Detailed explanation of Java 8 Lambda expression
2,java8 (II) lambda expression hand-in-hand learning
3,Detailed explanation of Lambda expression of Java SE8
4,Detailed explanation of Java8 lambda expression