Why use Lambda expressions
Let’s take a look at a few examples:
The first example is to execute a task in a separate thread, which we usually implement as follows:
class Worker implements Runnable { public void run() { for (int i = 0; i < 100; i++) doWork(); } ...}Worker w = new Worker(); new Thread(w).start();The second example is a custom string comparison method (by string length), which is generally done:
class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); }}Arrays.sort(strings, new LengthComparator());In the third example, in JavaFX, add a callback to a button:
button.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent event) { System.out.println("Thanks for clicking!"); }});These examples have one thing in common, which is that they first define a block of code, pass it to an object or method, and then execute it. Before Lambda expressions, Java does not allow direct passing of code blocks, because Java is object-oriented, so an object must be passed to encapsulate the code block to be executed into the object.
Lambda expression syntax
The LengthComparator in the second example above is expressed as a Lambda expression:
(String first, String second) -> Integer.compare(first.length(), second.length());
->Before is the parameter list, followed by the expression statement body;
If the body of an expression statement is more than one line, the body of the statement is written in {}, just like an ordinary function:
(String first, String second) -> { if (first.length() > second.length()) { return 1; } else if (first.length() == second.length()) { return 0; } else { return -1; }};If there are no parameters, () still needs to be brought with you. For example, the first example above can be expressed as:
() -> { for (int i = 0; i < 1000; i ++) { doWork(); }}If the type of the parameter can be automatically inferred from the context, you can omit:
Comparator<String> comp = (first, second) // Same as (String first, String second) -> Integer.compare(first.length(), second.length());
If there is only one parameter and the type can be automatically inferred, the brackets () can also be omitted:
// instead of (event) -> or (ActionEvent event) ->eventHandler<ActionEvent> listener = event -> System.out.println("Thanks for clicking!");The type of return value of the lambda expression is automatically inferred, so it does not need to be specified; in lambda expression, some conditional branches have return values, but other branches do not have return values, which is not allowed, such as:
(x) -> { if (x >= 0) { return 1; }}In addition, the difference between expression lambda and statement lambda is that expression lambda does not need to write the return keyword. Java runtime will return the result of the expression as the return value, while statement lambda is an expression written in {}, and the return keyword needs to be used, such as:
// expression lambdaComparator<String> comp1 = (first, second) -> Integer.compare(first.length(), second.length());// statement lambdaComparator<String> comp2 = (first, second) -> { return Integer.compare(first.length(), second.length());}; Functional Interface
If an interface has only one abstract method, it is called
Functional Interface, such as Runnable, Comparator, etc.
In any place where Functional Interface object is needed, you can use lambda expressions:
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length()));
Here, the second parameter of sort() requires a Comparator object, and Comparator is
Functional Interface, so you can directly pass in the lambda expression. When calling the compare() method of the object, it is to execute the statement body in the lambda expression;
If the statement of the lambda expression throws an exception, the corresponding abstract method in the Functional Interface must throw the exception, otherwise it is necessary to explicitly catch the exception in the lambda expression:
Runnable r = () -> { System.out.println("-------"); try { Thread.sleep(10); } catch (InterruptedException e) { // catch exception }};Callable<String> c = () -> { System.out.println("----------"); Thread.sleep(10); return "";}; Method Reference
If the parameters of the lambda expression are passed as parameters to a method and their execution effect is the same, the lambda expression can be expressed using Method Reference, and the following two methods are equivalent:
(x) -> System.out.println(x)System.out::println
Among them, System.out::println is called Method Reference.
Method Reference mainly comes in three forms:
For the first two methods, the corresponding lambda expression parameters and method parameters are the same, such as:
System.out::println(x) -> System.out.println(x)Math::pow (x, y) -> Math.pow(x, y)
For the third method, in the corresponding lambda expression statement body, the first parameter is used as an object, the method is called, and other parameters are used as the method parameters, such as:
String::compareToIgnoreCase(s1, s2) -> s1.compareToIgnoreCase(s2)1.5 Constructor Reference
The Constructor Reference is similar to the Method Reference, but it is a special method: new. The specific constructor is determined by the context environment, such as:
List<String> labels = ...;Stream<Button> stream = labels.stream().map(Button::new);
Button::new is equivalent to (x) -> Button(x), so the constructor called is: Button(x);
In addition to creating a single object, you can also create an array of objects, such as the following two equivalents:
int[]::new (x) -> new int[x]
Variable scope
Lambd expressions capture variables available in the current scope, such as:
public void repeatMessage(String text, int count) { Runnable r = () -> { for (int i = 0; i < count; i ++) { System.out.println(text); Thread.yield(); } }; new Thread(r).start();}But these variables must be immutable, why? See the following example:
int matches = 0;for (Path p : files) new Thread(() -> { if (p has some property) matches++; }).start(); // Illegal to mutate matchesBecause mutable variables are not thread-safe in lambda expressions, this is consistent with the requirements of inner classes, and only externally defined final variables can be referenced in inner classes;
The scope of the lambda expression is the same as that of the nested code block, so the parameter name or variable name in the lambd expression cannot conflict with local variables, such as:
Path first = Paths.get("/usr/bin");Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length()); // Error: Variable first already definedIf this variable is referenced in a lambda expression, the reference is this variable of the method that creates the lambda expression, such as:
public class Application() { public void doWork() { Runnable runner = () -> { ...; System.out.println(this.toString()); ... }; }} So here this.toString() calls toString() of the Application object, not Runnable
object's.
Default Method
There can only be abstract methods in the interface. If a new method is added to an existing interface, all implementation classes of the interface need to implement this method.
Java 8 introduces the concept of Default Method, and adds a default method to the interface, which will not destroy the existing interface rules. The interface implementation class can choose to override or directly inherit the default method, such as:
interface Person { long getId(); default String getName() { return "John Q. Public"; }}Java allows multiple inheritance. How to deal with this conflict if the methods defined in the parent class of a class are exactly the same as the default methods defined in the interface, or the two interfaces of a class are exactly the same, how to deal with this conflict? The processing rules are as follows:
If the method conflicts between the parent class and the interface: the methods in the parent class shall prevail, and the methods in the interface shall be ignored;
If the default method in the two interfaces conflicts, you need to override the method to resolve the conflict;
Static Method
Before Java 8, only static variables can be defined in the interface. Starting from Java 8, static methods can be added to the interface, such as
The Comparator interface has added a series of static methods of comparingXXX, such as:
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));}Using this static method, the following two methods are also equivalent:
1.
Arrays.sort(cities, (first, second) -> Integer.compare(first.length(), second.length()));
2.
Arrays.sort(cities, Comparator.comparingInt(String::length));
Therefore, when we design our own interfaces in the future, we no longer need to define separate tool classes (such as Collections/Collection).
Just use the static method in the interface.
Anonymous internal class
In the Java world, anonymous inner classes can implement operations that may only be performed once in an application. For example, in an Android application, a button click event is handled. You don't need to write a separate class to handle a click event, you can do this with an anonymous inner class:
Button button = (Button) findViewById(R.id.button1);button.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show(); } }); Lambda Example 1. Runnable Lambda Let’s look at several examples. Here is an example of Runnable: public void runnableTest() { System.out.println("=== RunnableTest ==="); // An 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!"); // Execute two run functions r1.run(); r2.run(); } public void runnableTest() { System.out.println("=== RunnableTest ==="); // An 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!"); // Execute two run functions r1.run(); r2.run(); } Neither implementation nor return value is returned. Runnable lambda expressions use code blocks to simplify the five-element code into one statement. public class Person { private String givenName; private String surName; private int age; private Gender gender; private String eMail; private String phone; private String address;} public class Person { private String givenName; private String surName; private int age; private Gender gender; private String eMail; private String phone; private String address;} The following is how to implement the Comparator interface using anonymous inner classes and Lambda expressions: public class ComparatorTest { public static void main(String[] args) { List<Person> personList = Person.createShortList(); // Use inner class to implement sorting Collections.sort(personList, new Comparator<Person>() { public int compare(Person p1, Person p2) { return p1.getSurName().compareTo(p2.getSurName()); } }); System.out.println("=== Sorted Asc SurName ==="); for (Person p : personList) { p.printName(); } // Implementation using Lambda expression // Ascending System.out.println("=== Sorted Asc SurName ==="); Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName())); for (Person p : personList) { p.printName(); } // Desc Subsequential System.out.println("=== Sorted Desc SurName ==="); Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName())); for (Person p : personList) { p.printName(); } }} public class ComparatorTest { public static void main(String[] args) { List<Person> personList = Person.createShortList(); // Use inner class to implement sorting Collections.sort(personList, new Comparator<Person>() { public int compare(Person p1, Person p2) { return p1.getSurName().compareTo(p2.getSurName()); } }); System.out.println("=== Sorted Asc SurName ==="); for (Person p : personList) { p.printName(); } // Implementation using Lambda expression // Ascending System.out.println("=== Sorted Asc SurName ==="); Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName())); for (Person p : personList) { p.printName(); } // Desc Subsequential System.out.println("=== Sorted Desc SurName ==="); Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName())); for (Person p : personList) { p.printName(); } }} You can see that anonymous inner classes can be implemented through Lambda expressions. Note that the first Lambda expression defines the type of the parameter as Person; the second Lambda expression omits the type definition. Lambda expressions support type knockdown, and if the required type can be deduced through the context, the type definition can be omitted. Here, since we use Lambda expressions in a Comparator that uses generic definitions, the compiler can deduce these two parameters as Person.