java Lambda expressions basic

One issue with anonymous(匿名的) classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem 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 this, to treat functionality as method argument, or code as data.


The previous section, Anonymous Classes, shows you how to implement a base class without giving it a name. Although this is often more concise<简洁> than a named class, for classes with only one method, even an anonymous class seems a bit excessive(过重) and cumbersome(笨拙累赘). Lambda expressions let you express instances of single-method classes more compactly( 紧凑).


This section covers the following topics:


Suppose that you are creating a social networking application. You want to create a feature that enables an administrator to perform any kind of action, such as sending a message, on members of the social networking application that satisfy certain criteria(标准). The following table describes this use case in detail:
Field Description
Name Perform action on selected members
Primary Actor Administrator
Preconditions Administrator is logged in to the system.
Postconditions Action is performed only on members that fit the specified criteria.
Main Success Scenario(剧本 设想)
  1. Administrator specifies criteria of members on which to perform a certain action.
  2. Administrator specifies an action to perform on those selected members.
  3. Administrator selects the Submit button.
  4. The system finds all members that match the specified criteria.
  5. The system performs the specified action on all matching members.
Extensions

1a. Administrator has an option to preview those members who match the specified criteria before he or she specifies the action to be performed or before selecting the Submitbutton.

Frequency of Occurrence Many times during the day.

Suppose that 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 that the members of your social networking application are stored in a List<Person> instance.

This section begins with a naive approach to this use case. It improves upon this approach with local and anonymous classes, and then finishes with an efficient(高效) and concise(简洁) approach using lambda expressions. Find the code excerpts described in this section in the example RosterTest.

public class RosterTest {

	
	interface CheckPerson{
        boolean test(Person p);
    }

    // Approach 1: Create Methods that Search for Persons that Match One Characteristic
    public static void printPersonsOlderThan(List<Person> roster, int age){
        for(Person p : roster){
            if(p.getAge() >= age){
                p.printPerson();
            }
        }
    }

    // Approach 2: Create More Generalized(概括归纳) Search Methods
    public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high){
        for(Person p : roster){
            if(low <= p.getAge() && p.getAge() <= high){
                p.printPerson();
            }
        }
    }

    // Approach 3: Specify Search Criteria(标准) Code in a Local Class
    // Approach 4: Specify Search Criteria Code in an Anonymous Class
    // Approach 5: Specify Search Criteria Code with a Lambda Expression
    public static void printPersons(List<Person> roster, CheckPerson tester) {
        for (Person p : roster) {
            if (tester.test(p)) {
                p.printPerson();
            }
        }
    }

    // Approach 6: Use Standard Functional Interfaces with Lambda Expressions
    //Predicate(断言)
    public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester){
        for(Person p : roster){
            if(tester.test(p)){
                p.printPerson();
            }
        }
    }
    // Approach 7: Use Lambda Expressions Throughout Your Application
    public static void processPersons(List<Person> roster, Predicate<Person> tester, Consumer<Person> block){
        for(Person p : roster){
        	if(tester.test(p)){
        		block.accept(p);
        	}
        }
    }
    // Approach 7, second example
    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);
    		}
    	}
    }
    
    // Approach 8: Use Generics(普通的) More Extensively(全面)
    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);
    		}
    	}
    }
	public static void main(String[] args) {
		List<Person> roster = Person.createRoster();
		// Approach 1: Create Methods that Search for Persons that Match One
        // Characteristic
        System.out.println("Persons older than 20:");
        printPersonsOlderThan(roster, 20);
        System.out.println();
        
        // Approach 2: Create More Generalized Search Methods
        System.out.println("Persons between the ages of 14 and 30:");
        printPersonsWithinAgeRange(roster, 14, 30);
        System.out.println();
        
        // Approach 3: Specify Search Criteria Code in a Local Class
<pre style="word-wrap: break-word; white-space: pre-wrap;">       // Approach 4: Specify Search Criteria Code in an Anonymous Class
        // Approach 5: Specify Search Criteria Code with a Lambda Expression
System.out.println("Persons who are eligible for Selective Service:"); class CheckPersonEligibleForSelectiveService implements CheckPerson { public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } } printPersons(roster, new CheckPersonEligibleForSelectiveService()); System.out.println(); // Approach 5: Specify Search Criteria Code with a Lambda Expression System.out.println("Persons who are eligible(有资格) for Selective Service " + "(lambda expression):"); printPersons( roster, (Person p) -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 ); System.out.println(); // Approach 6: Use Standard Functional Interfaces with Lambda // Expressions System.out.println("Persons who are eligible for Selective Service " + "(with Predicate parameter):"); printPersonsWithPredicate( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 ); System.out.println(); // Approach 7: Use Lamba Expressions Throughout Your Application System.out.println("Persons who are eligible for Selective Service " + "(with Predicate and Consumer parameters):"); processPersons(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.printPerson()); System.out.println(); // Approach 7, second example System.out.println("Persons who are eligible(有资格) for Selective Service " + "(with Predicate, Function, and Consumer parameters):"); processPersonsWithFunction( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) ); System.out.println(); // Approach 8: Use Generics More Extensively System.out.println("Persons who are eligible for Selective Service " + "(generic version):"); processElements( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println("processElements " + email) ); System.out.println(); // Approach 9: Use Bulk(大量的) Data Operations That Accept Lambda Expressions // as Parameters System.out.println("Persons who are eligible for Selective Service " + "(with bulk data operations):"); 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));}}
 
 
public class Person {

    public enum Sex {
        MALE, FEMALE
    }

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

    int age;
    public int getAge() {
       return age;
    }

    public void printPerson() {
        System.out.println(age+ "--" + name);
    }
    
    public String getEmailAddress(){
    	return emailAddress;
    }
    
    public static List<Person> createRoster(){
    	List<Person> data = new ArrayList<Person>();
    	for(int i= 1; i <= 10; i++){
    		Person p = new Person();
    		p.name = "aa" + i;
    		p.age = i*5;
    		p.emailAddress = "email " + i;
    		p.gender = i%2==0 ?Sex.FEMALE : Sex.MALE;
    		data.add(p);
    	}
    	return data;
    }

	public Sex getGender() {
		return gender;
	}
}


Approach 1: Create Methods that Search for Persons that Match One Characteristic

One simplistic(简化的) approach is to create several methods; each method searches for members that match one characteristic, such as gender or age. The following method prints members that are older than a specified age:

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

This approach can potentially(潜在的) make your application  brittle(易碎的) , which is the likelihood(可能性) of an application not working because of the introduction of updates (such as newer data types). Suppose that you upgrade(改进) your application and change the structure of the  Person  class such that it contains different member variables; perhaps the class records and measures ages with a different data type or algorithm(算法). You would have to rewrite a lot of your API to accommodate(适应) this change. In addition, this approach is unnecessarily restrictive(限制); what if you wanted to print members younger than a certain age


Approach 2: Create More Generalized(概括归纳) Search Methods
    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 want to print members of a specified sex, or a combination of a specified gender and age range? What if you decide to change the  Person  class and add other attributes such as relationship status or geographical(地理学的) location? Although this method is more generic(普遍) than  printPersonsOlderThan , trying to create a separate method for each possible search query can still lead to brittle code. You can instead separate the code that specifies the criteria(标准) for which you want to search in a different class.


Approach 3: Specify Search Criteria Code in a Local Class
        System.out.println("Persons who are eligible for Selective Service:");
        class CheckPersonEligibleForSelectiveService implements CheckPerson {
            public boolean test(Person p) {
                 return p.getGender() == Person.Sex.MALE
                     && p.getAge() >= 18
                     && p.getAge() <= 25;
             }
         }
        printPersons(roster, new CheckPersonEligibleForSelectiveService());
Although this approach is less brittle—you don't have to rewrite methods if you change the structure of the  Person —you still have additional code: a new interface and a local class for each search you plan to perform in your application. Because  CheckPersonEligibleForSelectiveService  implements an interface, you can use an anonymous class instead of a local class and bypass the need to declare a new class for each search.


Approach 4: Specify Search Criteria Code in an Anonymous Class

One of the arguments of the following invocation of the method printPersons is an anonymous class that filters members that are eligible for Selective Service in the United States: those who are male and 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 each search that you want to perform. However, the syntax of anonymous classes is bulky(笨重) considering that the CheckPerson 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.


Approach 5: Specify Search Criteria Code with a Lambda Expression

The CheckPerson interface is a functional interfaceA functional interface is any interface that contains only one abstract method. (A functional interface may contain one or more default methods or static methods.) Because a functional interface contains only one abstract method, you can omit(忽略) the name of that method when you implement it. To do this, instead of using an anonymous class expression, you use a lambda expression, which is highlighted in the following method invocation:

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

See Syntax of Lambda Expressions for information about how to define lambda expressions.

You can use a standard functional interface in place of the interface CheckPerson, which reduces even further the amount of code required.



Approach 6: Use Standard Functional Interfaces with Lambda Expressions

Reconsider the CheckPerson interface:

interface CheckPerson {
    boolean test(Person p);
}

This is a very simple interface. It's a functional interface because it contains only one abstract method. This method takes one parameter and returns a boolean value. The method is so simple that it might not be worth it to define one in your application. Consequently因此, the JDK defines several standard functional interfaces, which you can find in the package java.util.function.

For example, you can use the Predicate<T> interface in place of CheckPerson. This interface contains the method boolean test(T t):

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

The interface Predicate<T> is an example of a generic普通 interface. (For more information about generics, see the Generics (Updated) lesson.) Generic types (such as generic interfaces) specify one or more type parameters within angle三角 brackets方括号 (<>). This interface contains only one type parameter, T. When you declare or instantiate a generic type with actual type arguments, you have a parameterized type. For example, the parameterized type Predicate<Person> is the following:

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

This parameterized type contains a method that has the same return type and parameters as CheckPerson.boolean test(Person p). Consequently, you can use Predicate<T> in place ofCheckPerson as the following method demonstrates:

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

As a result, the following method invocation is the same as when you invoked printPersons in Approach 3: Specify Search Criteria Code in a Local Class to obtain members who are eligible for Selective Service:

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

This is not the only possible place in this method to use a lambda expression. The following approach suggests other ways to use lambda expressions.



Approach 7: Use Lamba Expressions Throughout Your Application

Reconsider the method printPersonsWithPredicate to see where else you could use lambda expressions:

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

This method checks each Person instance contained in the List parameter roster whether it satisfies the criteria specified in the Predicate parameter tester. If the Person instance does satisfy the criteria specified by tester, the method printPersron is invoked on the Person instance.

Instead of invoking the method printPerson, you can specify a different action to perform on those Person instances that satisfy the criteria specified by tester. You can specify this action with a lambda expression. Suppose you want a lambda expression similar to printPerson, one that takes one argument (an object of type Person) and returns void. Remember, to use a lambda expression, you need to implement a functional interface. In this case, you need a functional interface that contains an abstract method that can take one argument of type Person and returns void. The Consumer<T> interface contains the method void accept(T t), which has these characteristics. The following method replaces the invocation p.printPerson() with an instance of Consumer<Person> that invokes the method accept:

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

As a result, the following method invocation is the same as when you invoked printPersons in Approach 3: Specify Search Criteria Code in a Local Class to obtain members who are eligible for Selective Service. 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 want to do more with your members' profiles than printing them out. Suppose that you want to validate生效验证 the members' profiles or retrieve取回their contact information? In this case, you need a functional interface that contains an abstract method that returns a value. The Function<T,R> interface contains the method R apply(T t). The following method retrieves the data specified by the parametermapper, and then performs an action on it specified by the parameter block:

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 contained in roster who is eligible for Selective 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)
);


Approach 8: Use Generics(普通的) More Extensively(全面)

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 print the e-mail address of members who are eligible for Selective Service, invoke the processElements method as follows:

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

This method invocation performs the following actions:

  1. Obtains a source of objects from the collection source. In this example, it obtains a source of Person objects from the collection roster. Notice that the collection roster, which is a collection of type List, is also an object of type Iterable.
  2. Filters objects that match the Predicate object tester. In this example, the Predicate object is a lambda expression that specifies which members would be eligible for Selective Service.
  3. Maps each filtered object to a value as specified by the Function object mapper. In this example, the Function object is a lambda expression that returns the e-mail address of a member.
  4. Performs an action on each mapped object as specified by the Consumer object block. In this example, the Consumer object is a lambda expression that prints a string, which is the e-mail address returned by the Function object.

You can replace each of these actions with an aggregate operation.


Approach 9: Use Bulk(大量的) Data Operations That Accept Lambda Expressions as Parameters

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 maps each of the operations the method processElements performs with the corresponding aggregate operation:

processElements Action Aggregate Operation
Obtain a source of objects Stream<E> stream()
Filter objects that match a Predicate object Stream<T> filter(Predicate<? super T> predicate)
Map objects to another value as specified by a Function object <R> Stream<R> map(Function<? super T,? extends R> mapper)
Perform an action as specified by a Consumer object void forEach(Consumer<? super T> action)

The operations filtermap, and forEach are aggregate operations. Aggregate operations process elements from a stream, not directly from a collection (which is the reason why the first method invoked in this example is stream). A stream is a sequence of elements. Unlike a collection, it is not a data structure that stores elements. Instead, a stream carries values from a source, such as collection, through a pipeline. A pipeline is a sequence of stream operations, which in this example is filtermap-forEach. In addition, aggregate operations typically accept lambda expressions as parameters, enabling you to customize how they behave.

For a more thorough discussion of aggregate operations, see the  Aggregate Operations  lesson.



Lambda Expressions in GUI Applications

To process events in a graphical user interface (GUI) application, such as keyboard actions, mouse actions, and scroll actions, you typically create event handlers, which usually involves implementing a particular interface. Often, event handler interfaces are functional interfaces; they tend to have only one method.

In the JavaFX example HelloWorld.java (discussed in the previous section Anonymous Classes), you can replace the highlighted anonymous class with a lambda expression in this statement:

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

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

The method invocation btn.setOnAction specifies what happens when you select the button represented by the btn object. This method requires an object of type EventHandler<ActionEvent>. TheEventHandler<ActionEvent> interface contains only one method, void handle(T event). This interface is a functional interface, so you could use the following highlighted lambda expression to replace it:

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

Syntax of Lambda Expressions

A lambda expression consists of the following:

  • A comma-separated list of formal parameters enclosed in parentheses. The CheckPerson.test method contains one parameter, p, which represents an instance of the Person class.

    Note: You can omit the data type of the parameters in a lambda expression. In addition, you can omit the parentheses if there is only one parameter. For example, the following lambda expression is also valid:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
  • The arrow token, ->

  • A body, which consists of a single expression or a statement block. This example uses the following expression:

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

    If you specify a single expression, then the Java runtime evaluates the expression and then returns its value. Alternatively, you can use a return statement:

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

    A return statement is not an expression; in a lambda expression, you must enclose statements in braces ({}). However, you do not have to enclose a void method invocation in braces. For example, the following is a valid lambda expression:

    email -> System.out.println(email)

Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.

The following example, Calculator, is an example of lambda expressions that take more than one formal parameter:

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));    
    }
}

The method operateBinary performs a mathematical operation on two integer operands. The operation itself is specified by an instance of IntegerMath. The example defines two operations with lambda expressions, addition and subtraction. The example prints the following:

40 + 2 = 42
20 - 10 = 10

猜你喜欢

转载自blog.csdn.net/H291850336/article/details/51979591
今日推荐