Detailed Explanation of Lambda Expressions in Java

I. Overview

If you're a veteran of lambda expressions, you can skip the "one".

If you want to understand lambda expressions, then I suggest you start with "one".

Let's take you to understand Java's Lambda expressions from a simple example to a deeper level.

Two, an example

Let's explain the Java Lambda expression from the shallower to the deeper from a small example. We first prepare an interface and two classes.

First, we create an interface with an abstract method in the interface.

package com.dake.service;

public interface Printable {
    void print();
}

Second, create a Cat class and implement the Printable interface.

package com.dake.entity;

import com.dake.service.Printable;

public class Cat implements Printable {

    @Override
    public void print() {
        System.out.println("喵");
    }
}

Finally, we create a class and add a main method.

package com.dake.main;

import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {}
}

Now that the preparations are complete, let's start our journey of Java Lambda expressions.

package com.dake.main;

import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        Printable printable = new Cat();
        printable.print();
    }
}

This is a very simple piece of code that will print a "meow" on the console after running.

Now we add a static method to the Lambdas class and change the method of calling the interface.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        Printable printable = new Cat();
        printThing(printable);
    }

    static void printThing(Printable printable) {
        printable.print();
    }
}

 After running this way, a "meow" will still be printed.

Here we create a Cat object, and then pass the Cat object instance as a parameter to the printThing method for calling.

In the end, the printThing method actually executes the print method in the Cat object. Now we pass this method into printThing as a parameter.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        printThing(
            public void print() {
                System.out.println("喵");
            }
        );
    }

    static void printThing(Printable printable) {
        printable.print();
    }
}

It is obvious that the code will report an error.

If you remove the public void print, and then add an arrow like -> after (), you will find that the code does not report an error.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        printThing(
            () -> {
                System.out.println("喵");
            }
        );
    }

    static void printThing(Printable printable) {
        printable.print();
    }
}

Running this code still prints a "meow".

This is actually a Lambda expression. 

Here we ignore the modifier, remove the return type, and do not need the method name and parameter type, and add a "->" to the right of () , which becomes a Lambda expression.

Let's look at the above code, and the IDE has already given a hint, which is curly braces, and we can further optimize it.

We know that the curly braces part of the code is originally the method body of the print method, which can be called a statement Lambda . After we remove the curly braces of the method body, the code is still correct, and it becomes an expression Lambda at this time . Because there is only one sentence of code in our Lambda, it is just an expression.

At this point the code becomes like this:

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        printThing(() -> System.out.println("喵"));
    }

    static void printThing(Printable printable) {
        printable.print();
    }
}

We know from the above that the print method passed in the printThing method was originally the print method of the Cat class. After we simplified the operation, the code still runs normally. This is actually the true meaning of the Lambda expression: passing the method .

We know that, in general, only interfaces, classes (abstract classes, common classes—objects), variables, etc. can be passed in Java methods. At this time, we have achieved the method of passing methods in Java methods, which is Lambda expression.

At this point, we need to make a simple summary:

The essence of Lambda expressions is method passing. In fact, what is passed is a method. This method is like any other thing (interface, class (abstract class, ordinary class-object), variable), we can convert it into an object, as A variable is passed to the method as a parameter, but we remove the method modifier, return value type, method name, and finally add the Lambda expression sign "->" to the right of the method's brackets.

Since what is passed is a method implementation, we can convert it into an object and use it as a variable, then we can assign it to the instance of the class corresponding to the method, the interface of the instance of the class, and the abstract class.

The implementation of this method can only be the method implementation of the interface, not the method implementation of the abstract class. As for why, let's make a joke here.

At this point the code can be written like this and run, we can still print out a "meow".

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		Printable printable = () -> System.out.println("喵");
        printThing(printable);
    }

    static void printThing(Printable printable) {
        printable.print();
    }
}

At this point, we can comment out or delete the Cat class, because we have actually passed the print method of the Cat class into the printThing method, and finally formed our Lambda expression.

To sum it up in one sentence:

Through Lambda expressions, we have realized the functions that originally needed to be realized by the implementation class.

This is the most important feature of Lambda expressions.

Now our Lambda expression passes a print method with no parameters and no return value. Next, we add a parameter to the print method of the Printable interface. At this time, our printThing method will report an error because of our Lambda expression itself It is the implementation of the Printable interface method, but we do not pass parameters.

package com.dake.service;

public interface Printable {
    void print(String suffix);
}

We added a parameter suffix of type String to the print method of the Printable interface, but an error was reported when we called the printThing method, the reason we analyzed above.

This is easy to handle. We know that the () in this Lambda expression is actually the parentheses of the original normal method. If we want to add a parameter to this Lambda expression, then we can only add it in the parentheses.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		Printable printable = (s) -> System.out.println("喵");
        printThing(printable);
    }

    static void printThing(Printable printable) {
        printable.print();
    }
}

At this time, our printThing method also reported an error.

This is obvious, we used the print method of the Printable interface, but there is no parameter passing, it must be wrong. We add a parameter to it.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		Printable printable = (s) -> System.out.println("喵");
        printThing(printable);
    }

    static void printThing(Printable printable) {
        printable.print("!");
    }
}

When we execute the code, a "meow" will still be printed.

At this time, some friends will say that your print method has passed a Chinese exclamation mark, but why the print result is not displayed?

The reason is very simple. In the implementation of the print method, we passed the parameter to the Lambda expression, which is (s). This s is the exclamation mark we received, but we did not use it in the method body, that is, in the following code it.

System.out.println("喵");

This is actually the method body, but the curly braces are removed and it becomes a Java statement. We said above that this kind of Lambda is an expression Lambda.

We all know that in Java, a parameter is passed in a method, but we can not use it, of course we can use it, so it is the same in Lambda expressions.

Therefore, the final printed "meow" does not have an exclamation mark.

Next, let's add the parameters, put them in the printing method, and modify the code.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		Printable printable = (s) -> System.out.println("喵" + s);
        printThing(printable);
    }

    static void printThing(Printable printable) {
        printable.print("!");
    }
}

Print again.

We see that there is an extra Chinese exclamation mark after "喵", which is spliced ​​when printing the code.

How does Java know what this s is?

As we said above, Lambda expressions are method implementations, so the Java compiler naturally knows the parameter types defined in interfaces or abstract classes, so the implementation of interfaces, that is, our Lambda expressions here, must also have the same parameters type.

So the s here must be the parameter type of the method in the interface we modified before, that is, the String type.

Since Lambda expressions can be converted into objects or variables, they can naturally be passed to the printThing method. The code is modified as follows:

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		printThing((s) -> System.out.println("喵" + s));
    }

    static void printThing(Printable printable) {
        printable.print("!");
    }
}

The code also runs without problems.

 

In a Lambda expression, if it is a single parameter, the parentheses can be omitted, but if there are no parameters or multiple parameters, the parentheses cannot be omitted.

So the parentheses in the s in the above example can be omitted.

The print method in the Printable interface we tested above has no return value, that is, void. Now we modify the interface to return a String.

package com.dake.service;

public interface Printable {
    String print(String suffix);
}

At this time, the code in Lambdas will compile and report an error:

The error message said:

 Bad return type in lambda expression: void cannot be converted to String

We know that the code behind -> is actually the method body, just because our method body has only one sentence, so we optimized it, but in fact the real code should be like this:

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		printThing(s -> {
            System.out.println("喵" + s);
        });
    }

    static void printThing(Printable printable) {
        printable.print("!");
    }
}

They are equivalent, but we see the error message:

 Because the return type of the print method in the interface is string, but here we use a Lambda expression to implement the print method of the interface. The method body has only one printing method, but no return value, and an error will naturally be reported.

As we said above, this kind of curly braces is a statement lambda, and a lambda with only one expression is called an expression lambda without the curly braces.

According to the above, the two are actually equivalent, and the expression Lambda will also report an error, but the error message is different, but the essence is the same.

So, we come to a conclusion:

Whether there is a return value in the Lambda expression, and what type to return, are the same as those defined in the interface.

Because again, Lambda expressions are method implementations .

So how can we modify the above error?

There are two ways, one is for expression Lambda, and the other is for statement Lambda.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		printThing(s -> "喵" + s);

        printThing(s -> {
            System.out.println("喵" + s);
            return "喵" + s;
        });
    }

    static void printThing(Printable printable) {
        printable.print("!");
    }
}

Both of the above methods are available, except that one is not printed and the other is printed. The running result naturally only prints one.

Then how do we know that the expression Lambda can be written like that? Still the same sentence, because the return type defined in our interface is String, the expression Lambda of a statement just does not have the return keyword. Moreover, we cannot add this keyword, and adding return will report an error.

Therefore, if the expression Lambda has a return value, then the expression only needs to give the type defined in the interface, which can be a string, other basic data types, an object instance (for example, new Cat()), etc. And you can't write return.

Now we can add another parameter to the interface.

package com.dake.service;

public interface Printable {
    String print(String prefix, String suffix);
}

The code will immediately compile and report an error:

If we hold down Ctrl and left mouse button, the code immediately jumps to the printThing method:

 Obviously, there are two parameters in our interface, but it is one parameter when using the print method here, so an error will be reported naturally, so we add one.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
		printThing(s -> "喵" + s);

        printThing(s -> {
            System.out.println("喵" + s);
            return "喵" + s;
        });
    }

    static void printThing(Printable printable) {
        printable.print("泰迪", "!");
    }
}

The above two methods also reported errors:

The reason for the error is the same, we also add a parameter.

 

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        printThing((p, s) -> p + "喵" + s);
        printThing((p, s) -> {
            return p + "喵" + s;
        });
    }

    static void printThing(Printable printable) {
        printable.print("泰迪", "!");
    }
}

At this point we execute the code and nothing will be printed.

We can receive the return value of the interface in the printThing method and print it. Because the Lambda expression is the implementation of the interface, the result returned after the printThing method receives the method implementation can naturally print the result after the method is executed.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        printThing((p, s) -> p + "1喵" + s);
        printThing((p, s) -> {
            return p + "2喵" + s;
        });
    }

    static void printThing(Printable printable) {
        String taidi = printable.print("泰迪", "!");
        System.out.println(taidi);
    }
}

In the above example, we gave a piece of code that assigns a Lambda expression to an interface, but the code there is an expression Lambda, what if it is a statement Lambda? These two ways of writing are given below.

package com.dake.main;

import com.dake.entity.Cat;
import com.dake.service.Printable;

public class Lambdas {

    public static void main(String[] args) {
        Printable printable = (p, s) -> p + "喵" + s;

        Printable printable1 = (p, s) -> {
            System.out.println(p + "喵" + s);
            return p + "喵" + s;
        };
    }

    static void printThing(Printable printable) {
        String taidi = printable.print("泰迪", "!");
        System.out.println(taidi);
    }
}

We can see that the first expression Lambda does not need to return, it only needs to give the corresponding return value of the interface method, which is a string. The second statement Lambda needs a return, and there is a semicolon ending after the right curly brace, which represents the end of the method body, and this semicolon is indispensable, otherwise an error will be reported.

At this time, if the method is executed, nothing will be printed, because it is like we have implemented a method of an interface and finally returned, but there is no place to call it, of course nothing will be printed.

At this point, we have actually explained the Lambda expression from the shallower to the deeper through the code. But what we demonstrated above is just how to use it. We can't just know what it is, but also why it is.

We demonstrated above that the Lambda expression is assigned to the interface. This interface is actually a functional interface, but the Printable method we defined does not add annotations about the functional interface, it is just a common interface.

There is a note about functional interfaces:

package java.lang;

import java.lang.annotation.*;

/**
 * An informative annotation type used to indicate that an interface
 * type declaration is intended to be a <i>functional interface</i> as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * <em>not</em> count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 *
 * <p>Note that instances of functional interfaces can be created with
 * lambda expressions, method references, or constructor references.
 *
 * <p>If a type is annotated with this annotation type, compilers are
 * required to generate an error message unless:
 *
 * <ul>
 * <li> The type is an interface type and not an annotation type, enum, or class.
 * <li> The annotated type satisfies the requirements of a functional interface.
 * </ul>
 *
 * <p>However, the compiler will treat any interface meeting the
 * definition of a functional interface as a functional interface
 * regardless of whether or not a {@code FunctionalInterface}
 * annotation is present on the interface declaration.
 *
 * @jls 4.3.2 The Class Object
 * @jls 9.8 Functional Interfaces
 * @jls 9.4.3 Interface Method Body
 * @jls 9.6.4.9 @FunctionalInterface
 * @since 1.8
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

Through the above code, we can know that the FunctionalInterface annotation is only available in JDK1.8. Annotations The documentation notes are given above:

An informational annotation type used to indicate that the interface type declaration is a functional interface as defined by the Java Language Specification . Conceptually, a functional interface has only one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method to override a public method of java.lang.Object, then this also does not count towards the interface's abstract method count, since any implementation of the interface will have a method from java.lang.Oobject or other local realization.

Note that instances of functional interfaces can be created using lambda expressions, method references, or constructor references.

If a type is annotated with this annotation type, the compiler is required to generate an error message unless:

Types are interface types, not annotation types, enums or classes.

An annotated type satisfies the requirements of a functional interface.

However, the compiler treats any interface conforming to the definition of a functional interface as a functional interface, regardless of the presence or absence of the FunctionalInterface annotation on the interface declaration

The last statement marked in red above is exactly what we said above, our Printable interface is also a functional interface.

Through annotations, we can draw the following conclusions:

  1. A functional interface has only one abstract method
  2. The default method is not an abstract method
  3. Overriding methods in Object are not counted as abstract methods
  4. Instances of functional interfaces can be lambda expressions, method references, or constructor references
  5. Regardless of whether there is a FunctionalInterface annotation on the interface, as long as it conforms to the definition of a functional interface, it is a functional interface

An interface can still be a functional interface without using the FunctionalInterface annotation, and we can still use the corresponding Lambda expression. But if we add this annotation, there can only be one abstract method. If we add another abstract method, the compiler will report an error:

Multiple non-overridden abstract methods found in interface xxx

 Such interfaces are also called Sam interfaces because they have a single abstract method, or sam for short.
This method can contain other types of methods, such as static methods or default methods.

We mentioned above that we sold a pass:

It can only be the method implementation of the interface, not the method implementation of the abstract class.

Now let's untangle this.

First of all, through the term functional interface and the name of the annotation (FunctionalInterface), we know that this can only be an annotation that acts on the interface, so Lambda must only be the method implementation of the interface.

If we act on an abstract class, an error will be reported:

 

 

So, we come to another conclusion:

A functional interface can only be an interface, not an abstract class, and the FunctionalInterface annotation can only be applied to the interface.

So far, the Lambda expression is almost demonstrated, and the code is explained step by step to the final annotation. By now, basically some simple Lambda expressions should be understandable, and we can also write some functional interfaces and apply them to Lambda expressions.

It is also time for us to make a summary of the first part.

  1. Lambda expressions are method implementations of an interface. Remove modifiers, return types, method names, and parameter types, and add a "->" to the right of the parameter list (that is, parentheses), and it becomes a Lambda expression.
  2. The () of Lambda is the parameter list of the interface, no parameter type is required, only the parameter name is required.
  3. The curly braces of Lambda are the method implementation of the interface.
  4. Whether there is a return value in Lambda and what type it returns are the same as the methods defined in the interface.
  5. Lambdas with only one line of code can remove curly braces. If there is a return type, the return keyword is not required, and only a string or other data type or object instance corresponding to the interface return type is required.
  6. The curly braces at the end of the method body cannot be omitted.
  7. A lambda that has only one line of code and has its curly braces removed is called an expression lambda.
  8. Lambdas with curly braces are called statement lambdas.
  9. The essence of the whole Lambda is a method transfer, which can be converted into any object, such as interface, class, variable, etc., and can naturally be assigned to the corresponding object.
  10. Lambda expression implements the method that originally needs to be implemented in the interface implementation class.
  11. If the Lambda parameter is a single parameter, the parentheses can be omitted; if it is no parameter or multiple parameters, it cannot be omitted.
  12. The interface implemented by Lambda is a functional interface.
  13. A functional interface has only one abstract method.
  14. Default methods are not abstract methods.
  15. Overriding methods in Object do not count as abstract methods.
  16. Instances of functional interfaces can be lambda expressions, method references, or constructor references.
  17. Regardless of whether there is a FunctionalInterface annotation on the interface, as long as it conforms to the definition of a functional interface, it is a functional interface.
  18. A functional interface can only be an interface, not an abstract class.
  19. FunctionalInterface annotations can only be applied to interfaces, not to abstract classes.
  20. Functional interfaces are also called Sam interfaces because they have only one abstract method (single abstract method), referred to as sam.

Article reference:

Foreign big guys teach you the Lamda expressions that you must know when learning programming

Guess you like

Origin blog.csdn.net/qq_42971035/article/details/130187881