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:

  1. 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.
  2. An expression can be a single line or multiple statements. If there are multiple statements, enclose them in curly braces {}.
  3. 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?