This is the 22nd day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Why Lambda expressions
Let’s start with a few snippets of code that we used to encounter before Java8:
The thread is created and started
// Create a thread
public class Worker implements Runnable {
@Override
public void run(a) {
for (int i = 0; i < 100; i++) { doWork(); }}}// Start the thread
Worker w = new Worker();
new Thread(w).start();
Copy the code
To compare an array
// Define a comparator
public class LengthComparator implements Comparator<String> {
@Override
public int compare(String first, String second) {
returnInteger.compare(first.length(), second.length()); }}// Compare character arrays
Arrays.sort(words, new LengthComparator());
Copy the code
Add a click event to a button
public void onClick(Button button) {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked."); }}); }Copy the code
We’re used to these three pieces of code.
But their problem is also obvious: ** “noise” ** too much! It takes at least five lines of code to compare an array, but only one line of code is the one we really care about!
Java’s complex and redundant code implementation has long been criticized by programmers, but with the rise of JVM language Scala and the popularity of functional programming style, Oracle made revolutionary changes in the eighth series of Java, introduced a series of functional programming style syntax features. Examples include Lambda expressions and streams.
If Lambda expressions were used, the implementation of the above three pieces of code would be extremely concise.
Create a thread and start it (Lambda version)
new Thread(() -> {
for (int i = 0; i < 100; i++) {
doWork();
}
}).start();
Copy the code
Compare arrays (Lambda version)
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length())
Copy the code
Add click events to buttons (Lambda version)
button.addActionListener((event) -> System.out.println("button clicked."));
Copy the code
How’s that? With Lambda expressions, the code has become concise enough that you can focus solely on the business code.
Syntax for Lambda expressions
Format :(parameter) -> expression
Among them:
- Parameters can range from 0 to n. If there are multiple arguments, separate them with commas (,). If there is an argument, the parentheses () can be omitted; If there are no arguments, the parentheses () cannot be omitted. [This is a bit less pure, and a bit worse than Scala!] , the parameter can be preceded by the type name, but can be omitted due to the automatic type derivation function.
- An expression can be a single line or multiple statements. If there are multiple statements, enclose them in curly braces {}.
- The expression does not need to display the result of the execution; it is automatically derived from the context. Here are some examples:
A parameter
event -> System.out.println("button clicked.")
Copy the code
Multiple parameters
(first, second) -> Integer.compare(first.length(), second.length()
Copy the code
Zero parameters
() -> System.out.println("what are you nongshalei?")
Copy the code
Expression block
() - > {for (int i = 0; i < 100; i++) { doWork(); }}Copy the code
Functional interface
A new annotation has been added to Java8: @functionalinterface.
What is a functional interface? It contains the following characteristics:
- There is only one abstract method in the interface, but default and static methods are allowed.
- The @functionalInterface annotation is not required, but is recommended so that the compiler can check if there is only one abstract method in the interface.
Lambda expressions are essentially anonymous implementations of functional interfaces. The original interface implementation is expressed in a syntax more like functional programming.
The Java8 java.util.function package already has a number of functional interfaces built in, as shown below:
Functional interface | The parameter types | The return type | The method name | describe |
---|---|---|---|---|
Supplier | There is no | T | get | Produces a data of type T |
Consumer | T | void | accept | Consume data of type T |
BiConsumer<T,U> | T,U | void | accept | Consume data of type T and type U |
Function<T,R> | T | R | apply | The function converts data of type T into data of type R |
BiFunction<T,U,R> | T,U | R | apply | The function converts data of type T and U into data of type R |
UnaryOperator | T | T | apply | Unary operation on type T still returns type T |
BinaryOperator | T,T | T | apply | A binary operation on type T still returns type T |
Predicate | T | void | test | Performs function processing on type T, returning a Boolean value |
BiPredicate<T,U> | T,U | void | test | Performs function processing on types T and U, returning a Boolean value |
It can be seen that:
- There are four types of built-in functional interfaces: Supplier, Consumer, Function, and Predicate. Operator is a special case of Function.
- Except for Supplier, which does not provide binary arguments (which is related to Java’s inability to support multiple return values), all three classes provide binary input arguments.
Here is a comprehensive example:
public class FunctionalCase {
public static void main(String[] args) {
String words = "Hello, World";
String lowerWords = changeWords(words, String::toLowerCase);
System.out.println(lowerWords);
String upperWords = changeWords(words, String::toUpperCase);
System.out.println(upperWords);
int count = wordsToInt(words, String::length);
System.out.println(count);
isSatisfy(words, w -> w.contains("hello"));
String otherWords = appendWords(words, ()->{
List<String> allWords = Arrays.asList("+abc"."->efg");
return allWords.get(new Random().nextInt(2));
});
System.out.println(otherWords);
consumeWords(words, w -> System.out.println(w.split(",") [0]));
}
public static String changeWords(String words, UnaryOperator<String> func) {
return func.apply(words);
}
public static int wordsToInt(String words, Function<String, Integer> func) {
return func.apply(words);
}
public static void isSatisfy(String words, Predicate<String> func) {
if (func.test(words)) {
System.out.println("test pass");
} else {
System.out.println("test failed."); }}public static String appendWords(String words, Supplier<String> func) {
return words + func.get();
}
public static void consumeWords(String words, Consumer<String> func) { func.accept(words); }}Copy the code
If these built-in functional interfaces are not enough, you can also customize your own functional interfaces to meet more requirements.
Method references
If Lambda expressions already have methods to implement, they can be simplified using method references. The syntax for method references is as follows:
- Object :: instance method
- Class :: static methods
- Class :: instance methods
The aforementioned Lambda expression looks like this:
event -> System.out.println(event)
Copy the code
Can be replaced by:
System.out::println
Copy the code
Another example:
(x,y)->x.compareToIgnoreCase(y)
Copy the code
Can be replaced with:
String::compareToIgnoreCase
Copy the code
Note: method names cannot be followed by arguments! You can write system.out ::println, but not system.out ::println(” hello “)
If this is available, you can access it directly using the this:: instance method, or super:: instance method for the parent specified method.
Here’s an example:
public class Greeter {
public void greet(a) {
String lowcaseStr = changeWords("Hello,World".this::lowercase);
System.out.println(lowcaseStr);
}
public String lowercase(String word) {
return word.toLowerCase();
}
public String changeWords(String words, UnaryOperator<String> func) {
returnfunc.apply(words); }}class ConcurrentGreeter extends Greeter {
public void greet(a) {
Thread thread = new Thread(super::greet);
thread.start();
}
public static void main(String[] args) {
newConcurrentGreeter().greet(); }}Copy the code
Constructor reference
A constructor reference is similar to a method reference, except that the function interface returns an instance object or array. The syntax for a constructor reference is as follows:
- Class: : new
- Array: : new
Here’s an example:
List<String> labels = Arrays.asList("button1"."button2");
Stream<Button> stream = labels.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());
Copy the code
Labels.stream ().map(Button::new) = labels.stream().map(label->new Button(label))
Here’s another example of a constructor reference to an array type:
Button[] buttons = stream.toArray(Button[]::new);
Copy the code
Stream is converted directly to an array type, denoted by Button[]::new.
Variable scope
Let’s start with some code:
public void repeatMsg(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) { System.out.println(text); Thread.yield(); }}; }Copy the code
A lambda expression generally consists of three parts:
- parameter
- expression
- Free variables
Arguments and expressions are easy to understand. What are the free variables? These are the external variables referenced in lambda expressions, such as the text and count variables in the above example.
Lambda expressions are “closures”, as those familiar with functional programming will know. It’s just that Java8 isn’t called that. For free variables, Lambda expressions that require references are not allowed to change.
In Java anonymous inner classes, if you want to reference an external variable, the variable must be declared final. Although the free variable of a Lambda expression is not forced to be declared final, it is also not allowed to change.
For example:
public void repeatMsg(String text, int count) {
Runnable r = () -> {
while (count > 0) {
count--; // Error, cannot modify value of external variableSystem.out.println(text); }}; }Copy the code
In addition, Lambda expressions do not allow declarations of arguments or local variables with the same name as local variables. For example:
Path first = Paths.get("/usr/bin");
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
// Error, variable first is already defined
Copy the code
Default method in interface
Let’s start with why we added default methods to the Java8 interface.
For example, the designers of the Collection interface have added a forEach() method for traversing collections to make traversing collections more concise. Such as:
list.forEach(System.out::println());
Copy the code
However, if you add methods to the interface, the traditional approach is that all the custom implementation classes of the Collection interface implement the forEach() method, which is unacceptable to most existing implementations.
So the Java8 designers came up with the idea of adding a new method type, called default method, to the interface to provide a default method implementation, so that implementation classes that don’t implement methods can default to the implementation of the default method.
A use example:
public interface Person {
long getId(a);
default String getName(a) {
return "jack"; }}Copy the code
The addition of the default method can replace the previous classical interface and abstract class design, unified abstract method and default implementation are defined in the same interface. This is probably a skill stolen from Scala’s Trait.
Static methods in interfaces
In addition to default methods, Java8 also supports the definition of static methods and implementations in interfaces.
For example, before Java8, it was common to define a Paths utility class for the Path interface to implement the auxiliary methods of the interface through static methods.
Interface with a static method is easy to do, unified in an interface! Although this seems to break the original design idea of the interface.
public interface Path{
public static Path get(String first, String... more) {
returnFileSystem.getDefault().getPath(first, more); }}Copy the code
So the Paths class doesn’t make sense
summary
Unless you’re in a company that measures work by lines of code, what do you think?