Lambda Expression Of Java, lambdaexpression
Question
Read the JDK source code.Java. util. CollectionsInUnmodifiableCollectionClass saw such a piece of code:
public void forEach(Consumer<? super E> action) { c.forEach(action); }
WhileConsumerThe source code is as follows:
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
At first glance, I was puzzled, but I went back and found that this is not the new Lambda expression of Java 8. I used to understand these new features and did not notice that they were also used in the JDK source code. So I took the time to read the Java Lambda expression.
Lambda Algorithm
The non-formal expressions of Lambda calculus on wiki are as follows:
In the lambda algorithm, each expression represents a function. This function has a parameter and returns a value. Both parameters and return values are single-parameter functions. It can be said that there is only one "type" in the lambda algorithm, that is, this single-Parameter Function.
A function is defined anonymously using a Lambda expression. This expression describes the operations that the function performs on its parameters. For example, the "add 2" function f (x) = x + 2 can be expressed as lambda x. x + 2 (or λ y. y + 2, the parameter name does not matter) and the value of f (3) can be written (λ x. x + 2) 3. The application of the function is left-bound: f x y = (f x) y.
Consider such a function: it uses a function as a parameter, and this function will be used on 3: λ f. f 3. If we apply this (using the function as a parameter) function to our previous "add 2" function: (λ f. f 3) (λ x. x + 2), it is obvious that the following three expressions:
(λ f. f 3) (λ x. x + 2) and (λ x. x + 2) 3 and 3 + 2
Is equivalent. Functions with two parameters can be expressed through lambda calculus: the return value of a single parameter function is a single parameter function (see Currying ). For example, a function f (x, y) = x-y can write λ x. λ y. x-y. The following three expressions:
(λ x. λ y. x-y) 7 2 and (λ y. 7-y) 2 and 7-2
It is also equivalent. However, the equivalence between lambda expressions cannot be determined by a common function.
For more information about the formal expression, go to Lambda computing.
Lambda expressions in Java
In Java, Lambda expressions can have multiple parameters, and the form of Lambda expressions in Java in JSR335-FINAL (Java Specification Requests) is defined as follows:
LambdaExpression: LambdaParameters '->' LambdaBody LambdaParameters: Identifier '(' FormalParameterListopt ')' '(' InferredFormalParameterList ')' InferredFormalParameterList: Identifier InferredFormalParameterList ',' Identifier LambdaBody: Expression Block The following definitions from 8.4.1 are repeated here for convenience: FormalParameterList: LastFormalParameter FormalParameters ',' LastFormalParameter FormalParameters: FormalParameter FormalParameters, FormalParameter FormalParameter: VariableModifiersopt Type VariableDeclaratorId LastFormalParameter: VariableModifiersopt Type '...' VariableDeclaratorId FormalParameter
For example, Examples of lambda expressions:
() -> {} // No parameters; result is void () -> 42 // No parameters, expression body () -> null // No parameters, expression body () -> { return 42; } // No parameters, block body with return () -> { System.gc(); } // No parameters, void block body () -> { if (true) return 12; else { int result = 15; for (int i = 1; i < 10; i++) result *= i; return result; } } // Complex block body with returns (int x) -> x+1 // Single declared-type parameter (int x) -> { return x+1; } // Single declared-type parameter (x) -> x+1 // Single inferred-type parameter x -> x+1 // Parens optional for single inferred-type case (String s) -> s.length() // Single declared-type parameter (Thread t) -> { t.start(); } // Single declared-type parameter s -> s.length() // Single inferred-type parameter t -> { t.start(); } // Single inferred-type parameter (int x, int y) -> x+y // Multiple declared-type parameters (x,y) -> x+y // Multiple inferred-type parameters (final int x) -> x+1 // Modified declared-type parameter (x, final y) -> x+y // Illegal: can't modify inferred-type parameters (x, int y) -> x+y // Illegal: can't mix inferred and declared types
Note that the derived parameters and declared parameters cannot be mixed in the formal parameters.. (The type of Inferred-type parameters is Inferred from the upstream and downstream queries during compilation, for example, the parameter specified during definition)
Java SE 8: Lambda Quick Start
The following example is taken from Oracle-Java SE 8: Lambda Quick Start.
Runnable Lambda
public class LambdaTest { public static void main(String[] args) { LambdaTest LT = new LambdaTest(); LT.runnableTest(); LT.comparatorTest(); } public void runnableTest() { System.out.println("=== RunnableTest ==="); // Anonymous Runnable Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello world one!"); } }; // Lambda Runnable Runnable r2 = () -> { System.out.println("Hello world two!"); System.out.println("Hello world three!"); }; // Run em! r1.run(); r2.run(); } }
The above Code uses Lambda expressions to replace the definition of * New * operations and * run * methods, making the code more concise.
Comparator Lambda
class Person { public String surName; public Person(String surName) { super(); this.surName = surName; } public void printName() { System.out.println(this.surName); } } public void comparatorTest() { List<Person> personList = new ArrayList<Person>(); personList.add(new Person("B")); personList.add(new Person("A")); // // Sort with Inner Class // Collections.sort(personList, new Comparator<Person>() { // public int compare(Person p1, Person p2) { // return p1.surName.compareTo(p2.surName); // } // }); // // System.out.println("=== Sorted Asc SurName ==="); // for (Person p : personList) { // p.printName(); // } // Use Lambda instead // Print Asc System.out.println("=== Sorted Asc SurName ==="); Collections.sort(personList, (p1, p2) -> p1.surName.compareTo(p2.surName)); for (Person p : personList) { p.printName(); } // Print Desc System.out.println("=== Sorted Desc SurName ==="); Collections.sort(personList, (p1, p2) -> p2.surName.compareTo(p1.surName)); for (Person p : personList) { p.printName(); } }
Here, Lambda expressions are used to replace the anonymous object * Comparator.
Function
Previously, I thought that adding Lambda expressions was just a simple syntactic sugar, but more syntactic sugar was found later. Imagine if you need to judge and filter the data in a List, we usually follow the following general practice.
public class Person { public String givenName; public String surName; public int age; public Gender gender; public String eMail; public String phone; public String address; //getters and setters //... } public class RoboContactMethods2 { public void callDrivers(List<Person> pl){ for(Person p:pl){ if (isDriver(p)){ roboCall(p); } } } public boolean isDriver(Person p){ return p.getAge() >= 16; } public void roboCall(Person p){ System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); } }
In this way, if there is a need for multiple filtering conditions, more judgment functions need to be implemented. The more literary practices are as follows (or use the following Person object example ):
public interface MyTest<T> { public boolean test(T t); } public class RoboContactAnon { public void phoneContacts(List<Person> pl, MyTest<Person> aTest){ for(Person p:pl){ if (aTest.test(p)){ roboCall(p); } } } public void roboCall(Person p){ System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); } public static void main (String[] args) { //get PersonList for testing. List<Person> pl = getInitedPersonList(); RoboContactAnon rca = new RoboContactAnon(); rca.phoneContacts(pl, (p) -> p.getAge() > 16); } }
We use a custom * MyTest * interface, but we do not need to define this interface, because in Java SE 8, JDK provides us with a series of interfaces for our use. For example, our * MyTest * interface can be replaced by the * Predicte * interface provided by the system. Its definition is similar to MyTest:
public interface Predicate<T> { public boolean test(T t); }
BesidesPredicteJDK also provides a series of interfaces for use in different scenarios. They areJava. util. functionPackage. The following lists some interfaces provided by JDK:
- Predicate: A property of the object passed as argument - Consumer: An action to be performed with the object passed as argument - Function: Transform a T to a U - Supplier: Provide an instance of a T (such as a factory) - UnaryOperator: A unary operator from T -> T - BinaryOperator: A binary operator from (T, T) -> T
Collections
Except for the syntactic sugar mentioned aboveJava. util. functionIn addition to the package, Java SE 8 has been addedJava. util. streamThis is an enhancement to the Collections object. Consider the following scenario: "You need to filter elements in a List based on certain conditions, and then calculate the average value of an attribute of the filtered element ". Our practice is generally as follows:
// Still use List <Person> as an example, double sum = 0; int count = 0; for (Person p: personList) {if (p. getAge ()> 0) {sum + = p. getAge (); cout ++ ;}} double average = count> 0? Sum/average: 0;
If we use stream, we can use a more literary method:
// Get average of ages OptionalDouble averageAge = pl .stream() .filter((p) -> p.getAge() > 16) .mapToDouble(p -> p.getAge()) .average();
It can be seen that the code written in this way is indeed more concise.ListConvert toStreamAnd then perform operations on the elements. In addition, if we do not require the sequence of elements, we canStream ()Change MethodParallelStream ()Method to obtain a stream that can be processed in parallel. When we process elements, JVM will divide the stream and process each part in parallel, and then merge to improve the processing efficiency, which is transparent to developers.
Division, ing, and merging. Do you think you are familiar with this?MapReduce, Just followHadoopThe difference between processing nodes with machines is that Division processing is performed in a JVM. InJava. util. streamProvides us with a generalReduce ()Method:
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); int sumOfWeights = widgets.stream() .reduce(0, (sum, b) -> sum + b.getWeight()), Integer::sum);
Here, identity is the initial value of a reduce. If there is no element for reduce, the identitiy value is returned. If there is an element for reduce, It is accumulated on identity. Accumulator is a accumulators that accumulate partial results and other elements. combiner is a combiner that combines subresults of different parts to obtain the final results. If the operation is not required for element elements, we can use parallelStream () to obtain a parallel stream so that the stream can be divided and processed in parallel, make full use of the performance of these new features.
It is also possible to convert an input collection. For example, in the following example, the List of an attribute in the element is returned:
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); ArrayList<String> strings = stream.collect(() -> new ArrayList<>(), (c, e) -> c.add(e.toString()), (c1, c2) -> c1.addAll(c2)); List<String> strings = stream.map(Object::toString) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Maybe you think this is not a KV pair operation. Unlike MapReduce, You can Map the result into a Map. This is the correct KV pair, this operation requires the groupingBy (Collection collection Collection) method:
Collector<Employee, ?, Integer> summingSalaries = Collectors.summingInt(Employee::getSalary); Map<Department, Integer> salariesByDept = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment, summingSalaries));
The above is just a simple example of stream, please read the [JSR335-FINAL] (https://jcp.org/en/jsr/detail? Id = 335) (Java Specification Requests) about java. util. stream.
Postscript
At first I thought these new features were just syntactic sugar. Now I also think this feature is a syntactic sugar. Although this new feature is rarely used in the development process (or even not used), there is always no division for understanding these new features, the proper use of these new features can indeed achieve good results in some scenarios (concise code, excellent performance ). The original intention of this article is to record your own income and share it with you. If you do not write well or make mistakes, please criticize and correct them, communicate with each other, and make progress together.
References
Lambda algorithm-wikipedia
JSR335-FINAL (Java Specification Requests)
Oracle-Java SE 8: Lambda Quick Start
My Github