Advanced Java-- Lambda expression

Lambda Expressions

One problem with anonymous classes ( anonymous classes) is that if the implementation of the anonymous class is very simple, such as an interface containing only one method, then the syntax of the anonymous class may appear unwieldy and unclear. In these cases, you're usually trying to pass functionality as an argument to another method , such as what action should be taken when someone clicks a button. Lambda expressions enable you to do just that, treating functionality as method parameters, or code as data.

The previous section " Anonymous Classes " showed you how to implement it without giving the base class a name. While this is generally more concise than naming a class, for a class with only one method, even an anonymous class is a bit excessive and cumbersome. Lambda expressions allow more compact expression of instances of single-method classes.

This section covers the following topics:

1. Ideal use cases for Lambda expressions

Functional interface summary:

Consumer<T>

// 表示接受单个输入参数且不返回结果的操作。与大多数其他功能接口不同,Consumer 期望通过副作用来操作。
// 这是一个函数式接口,其函数式方法是accept(Object)。

@FunctionalInterface
public interface Consumer<T> {
    
    
	// 对给定参数执行此操作。
	void accept(T t);
	
	// 返回一个组合的Consumer,该Consumer按顺序执行此操作,然后执行after操作。
	// 如果执行任何操作都会抛出异常,则将其传递给组合操作的调用者。如果执行此操作会引发异常,
	// 则不会执行after操作。
	default Consumer<T> andThen(Consumer<? super T> after) {
    
    }
}

IntConsumer

Represents an operation that accepts a single integer parameter and returns no result . intThis is a basic type specialization of Consumer . Unlike most other functional interfaces, IntConsumerexpects to operate via side effects.

public interface IntConsumer {
    
    
	// 对给定参数执行此操作。
	void accept(int value);
}

Function<T, R>

// 表示接受一个参数并产生结果的函数。

@FunctionalInterface
public interface Function<T, R> {
    
    
	// 将此函数应用于给定参数。
	R apply(T t);
	
	// 返回一个组合函数,该函数首先将before函数应用于其输入,然后将此Function应用于结果。
	// 如果对任何一个函数的求值引发异常,则将其传递给组合函数的调用者。
	default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    
    }
		
	// 返回一个组合函数,该函数首先将此函数应用于其输入,然后将after函数应用于结果。
	// 如果对任何一个函数的求值引发异常,则将其传递给组合函数的调用者。
	default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    
    }
	
	// 返回一个总是返回其输入参数的function
	static <T> Function<T, T> identity() {
    
    }
}

BiFunction<T, U, R>

Represents a function that takes two arguments and produces a result. This is a binary specialization of the function.

This is a functional interface whose functional methods are apply(Object, Object).


public interface BiFunction<T, U, R> {
    
    
	// 将此函数应用于给定的参数。
	// t – the first function argument
	// u – the second function argument
	// Returns: the function result
	R apply(T t, U u);
}

BinaryOperator<T>

Indicates that an operation on two operands of the same type produces a result of the same type as the operands. This is BiFunctioa specialization of n for the case where both operands and result are of the same type.

public interface BinaryOperator<T> extends BiFunction<T,T,T> {
    
    
}

Comparator<T>

A comparison function that imposes a total ordering on some collection of objects. A comparator can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used to control the order of certain data structures (such as sorted setsor sorted maps), or to provide ordering for collections of objects that do not have a natural ordering.
An ordering imposed by a comparator con a set is said to be consistent Swith if and only if c.compare(e1, e2)==0 for Seach e1sum in it e2has e1.equals(e2)the same boolean value as and .equals

public interface Comparator<T> {
    
    
	// 比较它的两个参数的顺序。当第一个参数小于、等于或大于第
	// 二个参数时,返回负整数、零或正整数。
	// 在前面的描述中,符号`sgn(expression)`表示数学上的signum函数,
	// 它被定义为根据表达式的值是负、零还是正返回-1、0或1中的一个。
	int compare(T o1, T o2);
}

Supplier<T>

Indicates the provider of the result.
There is no requirement to return a new or different result each time the supplier is called.
This is a functional interface, and its functional methods are get().

public interface Supplier<T> {
    
    

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

ToIntFunction<T>

Represents inta function that produces a value result. This is Functionthe resulting intprimitive specialization.

This is a functional interface, and its functional methods are applyAsInt(Object).

@FunctionalInterface
public interface ToIntFunction<T> {
    
    

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(T value);
}

Suppose you are creating a social networking application. You want to create a feature that enables administrators to perform any type of action, such as sending a message, on members of a social networking application that meet certain criteria. The following table details this use case:

insert image description here
Assume members of this social networking application are represented by the following Person class:

public class Person {
    
    

    public enum Sex {
    
    
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
    
    
        // ...
    }

    public void printPerson() {
    
    
        // ...
    }
}

Suppose your social networking application has members stored in List<Person>the instance.

This section starts with a simple approach for this use case. It improves upon this approach using local and anonymous classes, and then provides an efficient and concise approach using lambda expressions. Find the code excerpts described in this section in the example RosterTest .

Method 1: Create a method to search for members matching a characteristic

A simple approach is to create several methods; each method searches for members matching a characteristic such as gender or age. The following method prints members over a specified age:

public static void printPersonsOlderThan(List<Person> roster, int age) {
    
    
    for (Person p : roster) {
    
    
        if (p.getAge() >= age) {
    
    
            p.printPerson();
        }
    }
}

Note: List is an ordered Collection . Collections are objects that combine multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregated data. For more information on collections, see Collections Tracking.

This approach may make your application fragile, i.e. the application may not work properly due to the introduction of updates such as newer data types. Suppose you upgrade your application and change Personthe structure of your class so that it contains a different member variable; perhaps the class records and measures age using a different data type or algorithm. You will have to rewrite a lot of API to accommodate this change. Also, this approach is unnecessarily restrictive; what if you wanted to print members younger than a certain age

Approach 2: Create a more general search method

The following method is printPersonsOlderThanmore general than ; it prints members within the specified age range:

public static void printPersonsWithinAgeRange(
    List<Person> roster, int low, int high) {
    
    
    for (Person p : roster) {
    
    
        if (low <= p.getAge() && p.getAge() < high) {
    
    
            p.printPerson();
        }
    }
}

What if you wanted to print members of a specified gender, or a combination of a specified gender and age range? What if you decided to change Personthe class and add additional attributes like relationship status or geographic location? Although this approach is more printPersonsOlderThangeneral than, But trying to create a separate method for each possible search query still leads to brittle code. Instead, you can separate the code that specifies the criteria to search for into different classes.

Method 3: Specify the search criteria code in the partial class

The following method prints the members that match the search criteria you specify:

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            p.printPerson();
        }
    }
}

This method checks whether each instance contained in the parameter list satisfies the search criteria specified in the parameter by tester.testcalling the method. If method tester. test returns a and then invokes the method on the instance.ListPersonCheckPersontestertrue PersonprintPersons

To specify search criteria, you can implement CheckPersonthe interface:

interface CheckPerson {
    
    
    boolean test(Person p);
}

The following classes testimplement CheckPersonthe interface by specifying the implementation of the methods. This method filters members who are eligible for selective service in the US: If Personthe parameter is male and aged between 18 and 25, it returns true:

class CheckPersonEligibleForSelectiveService implements CheckPerson {
    
    
    public boolean test(Person p) {
    
    
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

To use this class, you need to create a new instance of it and call printPersonsthe method:

printPersons(
    roster, new CheckPersonEligibleForSelectiveService());

Although this approach is less fragile ( Personyou don't have to override the method if you change the structure), it still requires extra code: every search you plan to perform in your application requires a new interface and a local class. Because CheckPersonEligibleForSelectiveServicean interface is implemented, you can use anonymous classes instead of local classes, and you don't need to declare a new class for each search.

Method 4: Specify the search criteria code in an anonymous class

One of the arguments to the method call below printPersonsis an anonymous class used to filter members who are eligible for selective service in the United States: male members between the ages of 18 and 25:

printPersons(
    roster,
    new CheckPerson() {
    
    
        public boolean test(Person p) {
    
    
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25;
        }
    }
);

This approach reduces the amount of code required because you don't have to create a new class for every search you want to perform. However, the syntax of an anonymous class is quite bulky, considering that CheckPersonan interface contains only one method. In this case, you can use a Lambda expression instead of an anonymous class, as described in the next section.

Method 5: Use Lambda expressions to specify search condition codes

CheckPersonThe interface is a functional interface ( functional interface). A functional interface is an interface that contains only one abstract method . (A functional interface may contain one or more default or static methods .) Because a functional interface contains only one abstract method, you can omit the method's name when implementing it . To do this, you can use a lambda expression instead of an anonymous class expression, which is highlighted in the following method call:

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

For information on how to define Lambda expressions, see Lambda Expression Syntax .

You can use a standard functional interface instead of CheckPersonthe interface, which further reduces the amount of code required.

Method 6: Standard Functional Interface Using Lambda Expressions

Rethink CheckPersonthe interface:

interface CheckPerson {
    
    
    boolean test(Person p);
}

This is a very simple interface. It is a functional interface because it contains only one abstract method . This method accepts one parameter and returns a boolean value. The method is so simple that it's probably not worth defining it in your application. Therefore, the JDK defines several standard functional interfaces, java.util.functionwhich you can find in packages.

For example, you can use Predicate<T>interfaces instead CheckPerson. This interface contains boolean test(T t):

interface Predicate<T> {
    
    
    boolean test(T t);
}

An interface Predicate<T>is an example of a generic interface. (See the Generics (Updated) lesson for more information on generics.) A generic type, such as a generic interface, <>specifies one or more type parameters inside angle brackets ( ). The interface contains only one type parameter T. When you declare or instantiate a generic type with actual type parameters, you have a parameterized type. For example, a parameterized type Predicate<Person>looks like this:

interface Predicate<Person> {
    
    
    boolean test(Person t);
}

This parameterized type contains a CheckPerson.boolean test(Person p)method with the same return type and parameters. Therefore, you can use Predicate<T>instead CheckPerson, as follows:

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            p.printPerson();
        }
    }
}

Therefore, the method call below is the same as Method 3: Specifying the search criteria code in a local class to obtain members eligible for the election service printPersons:

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

This is not the only place where lambda expressions are possible in the method. The methods below suggest other ways to use lambda expressions .

Method 7: Use Lambda expressions throughout the application

Rethink printPersonsWithPredicatethe method and see where else Lambda expressions can be used:

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            p.printPerson();
        }
    }
}

This method checks Listthat each instance contained in the parameter roster Personmeets the criteria specified in Predicatethe parameter . A method is invoked on an instance testerif Personthe instance meets the criteria specified by the tester .PersonprintPerson

Instead of calling printPersonthe method, you can specify a different action to perform on those Personinstances that meet the criteria specified by the tester. You can specify this action using a Lambda expression. Say you want a lambda expression like printPerson that takes one argument (an Personobject of type ) and returns it void. Remember, to use lambda expressions, you need to implement a functional interface. In this case, you need a functional interface containing an abstract method that takes Personone parameter of a type and returns it void. Consumer<T>An interface contains void accept(T t)methods, which have these characteristics. The following method replaces p.printPerson()the call with calling Consumer<Person>the instance's acceptmethod:

public static void processPersons(
    List<Person> roster,
    Predicate<Person> tester,
    Consumer<Person> block) {
    
    
        for (Person p : roster) {
    
    
            if (tester.test(p)) {
    
    
                block.accept(p);
            }
        }
}

Therefore, the method call below is the same as Method 3: Specifying search criteria code in a local class for members eligible for election service printPersons. The lambda expression used to print members is highlighted:

processPersons(
     roster,
     p -> p.getGender() == Person.Sex.MALE
         && p.getAge() >= 18
         && p.getAge() <= 25,
     p -> p.printPerson()
);

What if you wanted to do more with the member's profile than print it out ? Suppose you wanted to validate a member's profile or retrieve their contact information? In that case, you'd need an abstraction that includes the return value The functional interface for the method. Function<T,R> Interfaces contain methods R apply(T t). The following methods retrieve mapperthe data specified by the parameters and then blockperform operations on the data specified by the parameters:

public static void processPersonsWithFunction(
    List<Person> roster,
    Predicate<Person> tester,
    Function<Person, String> mapper,
    Consumer<String> block) {
    
    
    for (Person p : roster) {
    
    
        if (tester.test(p)) {
    
    
            String data = mapper.apply(p);
            block.accept(data);
        }
    }
}

The following method retrieves the email address from each member of the roster that is eligible for the select service, and then prints it:

processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

Method 8: Use Generics More Extensively

Rethink processPersonsWithFunctionthe approach. Here is a generic version of it that accepts a collection containing elements of any data type as an argument:

public static <X, Y> void processElements(
    Iterable<X> source,
    Predicate<X> tester,
    Function <X, Y> mapper,
    Consumer<Y> block) {
    
    
    for (X p : source) {
    
    
        if (tester.test(p)) {
    
    
            Y data = mapper.apply(p);
            block.accept(data);
        }
    }
}

To list the email addresses of members who are eligible to participate in voting services, call the method as follows processElements:

processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

This method call does the following:

  1. sourceGet the object source from the collection . In this case, it gets Personthe source of the object from the collection roster. Note that collection rosters are Listcollections of types, and Iterableobjects of types.
  2. Filters objects that match Predicate an object . testerIn this case, Predicatethe object is a lambda expression that specifies which members are eligible to participate in the election service.
  3. Maps each filter object to the value specified by Functionthe object . mapperIn this case, Functionthe object is an lambdaexpression that returns a member's email address.
  4. Executes one operation specified by Consumer the object on each mapped object . blockIn this case, Consumerthe object is an lambdaexpression that outputs a string, Functionthe email address that the object returns.

You can replace each of these operations with aggregate operations.

Method 9: Use aggregate operations that accept Lambda expressions as parameters

The following example uses an aggregate operation to print roster the email addresses of members of a collection who are eligible for election:

roster
    .stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

The following table proceselementmaps each operation performed by the method with the corresponding aggregate operation:
insert image description here
Operation filter, mapand forEachisAggregateOperation( aggregate operations). Aggregate operations work on streamelements from a stream ( ), not directly from a collection (which is why the first method called in this example is stream). A stream ( stream) is a sequence of elements. Unlike a set, it is not a data structure that stores elements. Instead, streams carry values ​​from sources such as collections through pipes. A pipeline ( pipeline) is a sequence of stream operations, in this case filter- map-forEach. Additionally, aggregate operations often accept Lambdaexpressions as arguments, enabling you to customize how they behave.

For a more detailed discussion of aggregate operations, see the lesson on aggregate operations .

1.1 Lambda expressions in GUI applications

To handle events in a graphical user interface (GUI) application, such as key presses, mouse actions, and scrolling actions, you usually need to create event handlers, which usually involve implementing a specific interface. Typically, event handler interfaces are functional interfaces; they often have only one method.

In the JavaFX example HelloWorld.java (discussed in the previous section on anonymous classes ), you can replace the highlighted anonymous class with a Lambda expression in the following statement:

		btn.setOnAction(new EventHandler<ActionEvent>() {
    
    

            @Override
            public void handle(ActionEvent event) {
    
    
                System.out.println("Hello World!");
            }
        });

The method call specifies what happens when btn.setOnActionyou select the button represented by the object. btnThis method takes an EventHandler<ActionEvent>object of type. EventHandler<ActionEvent>An interface contains only one method, void handle(T event). This interface is a functional interface, so you can replace it with a lambda expression as highlighted below:

 btn.setOnAction(
          event -> System.out.println("Hello World!")
        );

1.2 Syntax of Lambda expressions

LambdaAn expression consists of the following:

1. A comma-separated list of formal parameters enclosed in parentheses.

CheckPerson.testThe method contains one parameter p, which represents Personan instance of the class.
Note: The data types of parameters in lambda expressions can be omitted . Also, parentheses can be omitted if there is only one parameter . For example, the following lambda expressions are also valid:

p -> p.getGender() == Person.Sex.MALE 
    && p.getAge() >= 18
    && p.getAge() <= 25

2. Arrow mark->

3. The body, consisting of a single expression or block of statements.

This example uses the following expression:

p.getGender() == Person.Sex.MALE 
    && p.getAge() >= 18
    && p.getAge() <= 25

If a single expression is specified, the Java runtime evaluates that expression and returns its value. Alternatively, you can use returnthe statement:

p -> {
    
    
    return p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25;
}

returnStatements are not expressions; in lambda expressions, statements must be {}enclosed in curly braces ( ) . However, you don't have to enclose the void method call in curly braces. For example, the following is a valid lambda expression:

email -> System.out.println(email)

Note that Lambdaan expression looks a lot like a method declaration; you can think of Lambdaan expression as an anonymous method -- a method without a name.

The following example Calculatoris an example of a lambda expression with multiple formal parameters:

public class Calculator {
    
    
  
    interface IntegerMath {
    
    
        int operation(int a, int b);   
    }
  
    public int operateBinary(int a, int b, IntegerMath op) {
    
    
        return op.operation(a, b);
    }
 
    public static void main(String... args) {
    
    
    
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));    
    }
}

operateBinarymethod performs a mathematical operation on two integer operands. The operation itself IntegerMathis specified by an instance of . This example lambdadefines two operations using expressions: additionand subtraction. The example prints the following:

40 + 2 = 42
20 - 10 = 10

1.3 Accessing local variables in enclosing scopes

Like local and anonymous classes, lambda expressions can capture variables ; they have the same access as local variables of the enclosing scope. However, unlike local and anonymous classes, lambda expressions don't have any shadowing issues (see Shadowing for more information ). Lambda expressions have lexical scope. This means they do not inherit any names from the supertype, nor do they introduce new levels of scope . Declarations in lambda expressions are interpreted in the same way as they are in the enclosing environment. The following example LambdaScopeTest demonstrates this:

import java.util.function.Consumer;
 
public class LambdaScopeTest {
    
    
 
    public int x = 0;
 
    class FirstLevel {
    
    
 
        public int x = 1;
        
        void methodInFirstLevel(int x) {
    
    

            int z = 2;
             
            Consumer<Integer> myConsumer = (y) -> 
            {
    
    
                // The following statement causes the compiler to generate
                // the error "Local variable z defined in an enclosing scope
                // must be final or effectively final" 
                //
                // z = 99;
                
                System.out.println("x = " + x); 
                System.out.println("y = " + y);
                System.out.println("z = " + z);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };
 
            myConsumer.accept(x);
 
        }
    }
 
    public static void main(String... args) {
    
    
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

The example produces the following output:

insert image description here
If you substitute myConsumerparameters in the declaration of the lambda expression , the compiler will generate an error:xy

Consumer<Integer> myConsumer = (x) -> {
    
    
    // ...
}

The compiler generates an error " Lambda表达式的参数x不能重新声明在封闭作用域中定义的另一个局部变量" because the lambda expression does not introduce a new level of scope. Therefore, you can directly access fields, methods, and local variables of the enclosing scope. For example, lambda expressions directly access methodInFirstLevelmethod parameters x. To access variables in the enclosing class, use keywords this. In this example, this xpoints to a member variable FirstLevel.x.

However, like local and anonymous classes, lambda expressions can only access final or effectively final local variables and parameters of the enclosing block . In this example, the variable z is actually final; once initialized, its value never changes. However, suppose you myConsumeradd the following assignment statement to the lambda expression:

Consumer<Integer> myConsumer = (y) -> {
    
    
    z = 99;
    // ...
}

Because of this assignment statement, the variable z is effectively no longer final. As a result, the Java compiler generates an error message similar to "Local variable z defined in enclosing scope must be final or effectively final".

1.4 Target Type

How do you determine the type of lambda expression? Recall the lambda expression that selects male members between the ages of 18 and 25:

p -> p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25

This lambda expression is used in the following two methods:

  • public static void printPersons(List<Person> roster, CheckPerson tester)(Method 3: Specify the search criteria code in the partial class)
  • public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)(Approach 6: Using standard functional interfaces and Lambda expressions)

When the Java runtime invokes printPersonsa method, it expects a data type of CheckPerson, so the lambda expression is of that type. However, when the Java runtime invokes printPersonsWithPredicatethe method, it expects the data type to be Predicate<Person>, so the lambda expression is of that type. The data type expected by these methods is called the target type ( target type). To determine the type of a lambda expression, the Java compiler uses the object type of the context or case in which the lambda expression appears . Next, you can only use lambda expressions if the Java compiler can determine the target type:

  • Variable declarations

  • Assignments

  • Return statements

  • Array initializers

  • Method or constructor arguments

  • Lambda expression bodies

  • Conditional expressions, ?:

  • Cast expressions

Target type and method parameters

For method parameters, the Java compiler uses two other language features to determine the target type: overload resolution and type parameter inference .

Consider the following two functional interfaces ( java.lang.Runnable and java.util.concurrent.Callable) :

public interface Runnable {
    
    
    void run();
}

public interface Callable<V> {
    
    
    V call();
}

The method Runnable.rundoes not return a value, but Callable<V>.callreturns

Suppose you have overloaded the method as follows invoke (see Defining Methods for more information on overloading methods ):

void invoke(Runnable r) {
    
    
    r.run();
}

<T> T invoke(Callable<T> c) {
    
    
    return c.call();
}

Which method will be called by the following statement?

String s = invoke(() -> "done");

The method invoke(Callable<T>)will be called because the method returns a value; the method invoke(Runnable)will not. In this case, ()-> "done"the type of the lambda expression is Callable<T>.

1.5 Serialization

A lambda expression can be serialized if its target type and the parameters it captures are serializable . However, as with inner classes , serialization of lambda expressions is strongly discouraged .

2. Method reference

You can use lambda expressions to create anonymous methods . However, sometimes lambda expressions do nothing but call an existing method. In these cases, it is often clearer to refer to an existing method by name. Method references enable you to do this; they are concise, easy-to-read lambda expressions for methods that already have names.

Consider again the Person class discussed in the section on Lambda expressions :

public class Person {
    
    

    // ...
    
    LocalDate birthday;
    
    public int getAge() {
    
    
        // ...
    }
    
    public LocalDate getBirthday() {
    
    
        return birthday;
    }   

    public static int compareByAge(Person a, Person b) {
    
    
        return a.birthday.compareTo(b.birthday);
    }
    
    // ...
}

Suppose your social networking application has members contained in an array, and you want to sort the array by age. You can use the following code ( find an excerpt of the code described in this section in the example MethodReferencesTest ):

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    
    
    public int compare(Person a, Person b) {
    
    
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

sortThe method signature of the call is as follows:

static <T> void sort(T[] a, Comparator<? super T> c)

Note that the interface Comparatoris a functional interface. Therefore, instead of defining and creating a Comparatornew instance of an implementing class, you can use a lambda expression:

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
    
    
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

However, the method for comparing the date of birth of two Person instances already Person.compareByAgeexists. You can call this method in the body of a lambda expression:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

Because this lambda expression invokes an existing method, you can use method references instead of lambda expressions :

Arrays.sort(rosterAsArray, Person::compareByAge);

Method references are Person::compareByAgesemantically (a, b) -> Person.compareByAge(a, b)identical to lambda expressions. Each has the following characteristics:

  • Its formal parameter list is copied from Comparator<Person>.compare, ie (Person, Person).
  • its body calls Person.compareByAgethe method

Types of method references:

There are four types of method references:

insert image description here
The following sample MethodReferencesExamples contain examples of the first three types of method references:

import java.util.function.BiFunction;

public class MethodReferencesExamples {
    
    
    
    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
    
    
        return merger.apply(a, b);
    }
    
    public static String appendStrings(String a, String b) {
    
    
        return a + b;
    }
    
    public String appendStrings2(String a, String b) {
    
    
        return a + b;
    }

    public static void main(String[] args) {
    
    
        
        MethodReferencesExamples myApp = new MethodReferencesExamples();

        // Calling the method mergeThings with a lambda expression
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", (a, b) -> a + b));
        
        // Reference to a static method
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        // Reference to an instance method of a particular object        
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", myApp::appendStrings2));
        
        // Reference to an instance method of an arbitrary object of a
        // particular type
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", String::concat));
    }
}

System.out.println()The output of all statements is the same:Hello World!

BiFunctionIs one of many functional interfaces in the java.util.function package. A functional interface can represent a lambda expression or method reference that takes two arguments and produces a result.

referencing a static method

Method references Person::compareByAgeand MethodReferencesExamples::appendstringare references to static methods.

A reference to an instance method of a specific object

Here's an example that refers to an instance method of a specific object:

class ComparisonProvider {
    
    
    public int compareByName(Person a, Person b) {
    
    
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
    
    
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

A method reference myComparisonProvider::compareByNameinvokes a method compareByName, which is myComparisonProviderpart of the object. The JRE infers the method type parameters, in this case it is (Person, Person).

Similarly, a method reference invokes myApp::appendStrings2a method appendStrings2, which is myApppart of the object. The JRE infers the method type parameters, in this case it is (String, String).

Refers to an instance method of any object of a particular type

The following is an example of a reference to an instance method of an arbitrary object of a specific type:

String[] stringArray = {
    
     "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

The equivalent lambda expression for a method String::compareToIgnoreCasereference would have a list of formal parameters (String a, String b), where a and b are arbitrary names used to better describe this example. A method reference will invoke a.compareToIgnoreCase(b)the method.

Similarly, a method reference String::concatwill invoke a.concat(b)a method.

constructor reference

You can refer to a constructor like a static method, by using the name new. The following method copies elements from one collection to another:

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
    
    
        
    DEST result = collectionFactory.get();
    for (T t : sourceCollection) {
    
    
        result.add(t);
    }
    return result;
}

A functional interface Suppliercontains a method getthat takes no parameters and returns an object . Therefore, you can call transferElementsmethods with lambda expressions like this:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> {
    
     return new HashSet<>(); });

You can use constructor references instead of lambda expressions, like so:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

The Java compiler deduced that you wanted to create a collection containing Personelements of type HashSet. Alternatively, you can specify:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

Guess you like

Origin blog.csdn.net/chinusyan/article/details/130934246