Java 1.8 新特性之(Lambda表达式) - Go语言中文社区

Java 1.8 新特性之(Lambda表达式)


译文原址:Lambda Expressions 

前言:本人英语水平不高,在此翻译官网文章纯属个人兴趣,肯定有些翻译不合理的地方,请大家即使指出,万分感谢!!

  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.

  如果一个匿名类的实现非常简单,譬如一个接口中只包含一个方法,那么这个匿名类的语法看起来就显得有些笨拙并且也不清晰。在这些情况下,你通常会试图将函数作为参数传递给另一方法,例如当按钮被按下时应该采取什么行动。Lambda表达式使你能做到这一点,将函数视为方法参数,或代码作为数据。
  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.

  前面已经提及了如何实现匿名的基本类,虽然这通常对于只包含一个方法的类来说,比通过显示命名在使用更简洁,不过即使是一个匿名类似乎也有点过多和麻烦。Lambda表达式可以让只有一个方法的类的实例更简洁。

Ideal Use Case for Lambda Expressions(Lambda表达式理想使用情况)

  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 Submit button.管理员可以在指定要执行的操作或选择提交按钮之前预览与指定条件匹配的成员。

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.
  假设你的社交网络应用程序的成员存储在List<Person>实例中。

  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.

  这部分我们首先使用一个幼稚的方法。它改善了使用当地和匿名类的方法,然后完成一个简洁的使用Lambda表达式的方法。我们可以在示例RosterTest中找到这部分描述的代码片段。

Approach方法 1: Create Methods That Search for Members 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();
        }
    }
}

Note: A List is an ordered Collection. A collection is an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data. For more information about collections, see theCollections trail.

注意:List 是有序集合。集合是将多个元素组合成单个单元的对象。集合用于存储、检索、操作和通信聚合数据。有关集合的更多信息,请参见Collections trail。

  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, for example?

  这个方法可能会使应用程序变得脆弱,存在应用程序无法工作的可能性,原因是应用程序的更新(例如更新的数据类型)。假设您升级了应用程序并更改了类的结构,使其包含不同的成员变量;你也将不得不重写很多API来适应这种变化。此外,这种方法包含了不必要的限制,例如:如果你想打印的成员比一定年龄的年轻?

Approach 2: Create More Generalized Search Methods 创建更广义的搜索方法

The following method is more generic than printPersonsOlderThan; it prints members within a specified range of ages:

下面的方法比printpersonsolderthan更通用的;它将打印指定范围内的成员的年龄:

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.
  如果您想打印指定性别的成员,或指定性别和年龄范围的组合,该怎么办?如果您决定更改人物类并对其添加其他属性,如关系状态或地理位置,该怎么办?虽然这种方法比printpersonsolderthan更通用,努力创造一个单独的方法来实现每一个可能的搜索查询,然而仍然可能导致代码比较脆弱。你可以在不同的类中指定不同搜索条件,从而将其分隔开。

Approach 3: Specify Search Criteria Code in a Local Class 在内部类中指定搜索条件代码

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

下面的方法打印与指定的搜索条件相匹配的成员:

public static void printPersons(
    List<Person> roster, CheckPerson 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 search criteria specified in the CheckPerson parameter tester by invoking the method tester.test. If the method tester.test returns a true value, then the method printPersons is invoked on the Person instance.
  这个方法会通过调用方法tester.test检查roster 中包含的每一个Person 实例是否满足tester 指定的搜索条件。如果tester.test方法返回true,printPersons 方法则会执行Person实例。

To specify the search criteria, you implement the CheckPerson interface:

为了指定搜索条件,你需要实现CheckPerson 接口:

interface CheckPerson {
    boolean test(Person p);
}

  The following class implements the CheckPerson interface by specifying an implementation for the method test. This method filters members that are eligible for Selective Service in the United States: it returns a true value if its Person parameter is male and between the ages of 18 and 25:
  下面的这个类实现了CheckPerson 接口,并指定了test方法的具体实现。此方法筛选在美国有资格进行选择性服务的成员:如果其参数为男性,年龄在18岁至25岁之间,则返回真实值:

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 create a new instance of it and invoke the printPersons method:

使用这个类,你可以创建它的一个新的实例并调用printpersons方法:

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.
  尽管这种方法不那么脆弱,如果你改变了Person的结构你不必重写方法了,但是你还需要额外添加代码:你计划在你的应用程序中执行的新的接口和内部类。因为CheckPersonEligibleForSelectiveService 实现了一个接口,你可以使用一个匿名类而不是内部类并且绕过了为每一个搜索声明一个新类。

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 方法中包含一个只有一个参数的匿名函数,其作用是过滤在美国有选择服务资格的人的条件:男性以及年龄在18岁至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.
  此方法减少了所需代码的数量,因为您不必为要执行的每个搜索创建新类。然而,匿名类的语法是考虑到checkperson接口只包含一个方法笨重。在这种情况下,你可以使用一个而不是一个匿名类的lambda表达式,如在下一节中描述。

Approach 5: Specify Search Criteria Code with a Lambda Expression

  The CheckPerson interface is a functional interface(函数式). A 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:

  CheckPerson 接口是一个函数式接口。一个函数式接口是只包含一个抽象方法的接口。(一个函数式接口中可以包含一个或多个默认方法或者静态方法)因为一个函数接口只包含一个抽象方法,所以当你实现它时,可以省略该方法的名称。要做到这一点,而不是使用一个匿名类的表达,你使用lambda表达式,由以下方法调用了:

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.可以查看lambda表达式的语法来了解如何定义lambda表达式。

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

  你可以在界面checkperson处使用一个标准的功能接口,从而进一步降低所需的代码。

Approach 6: Use Standard Functional Interfaces with Lambda Expressions 使用标准的功能接口与Lambda表达式

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.
  这是一个非常简单的接口。他是一个函数式接口因为它只包含了一个抽象方法。这个方法接收一个参数并返回一个布尔值。方法很简单,在应用程序中定义它可能不值得。

因此,JDK中定义了几个标准的函数式接口,你可以在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):

  举例来说,你可以使用Predicate<T>接口来替换掉CheckPerson。这个接口包含方法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:  接口Predicate<T>是一个泛型接口。(更多关于泛型的内容,参见Generics (Updated))。泛型类型(如泛型接口)是在尖括号中指定一个或多个类型参数。这个接口中只包含一个类型参数T。当你使用一个实际类型参数声明或实例化一个类属性,你就有一个参数化的 类型。例如,参数化类型Predicate<Person> :

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 of CheckPerson as the following method demonstrates:

  这种参数化的类型包含一个返回值类型以及参数与CheckPerson.boolean test(Person p)一样的方法。因此,你可以在使用Predicate<T>替换掉CheckPerson ,就像下面方法演示一样:

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:
  因此,下面的方法调用与你调用Approach 3: Specify Search Criteria Code in a Local Class中的printPersons是一样,用来获得有选择服务资格的会员:

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.

  这并不是在该方法中唯一可能使用Lambda表达式的地方。以下方法举例了的其它方式使用lambda表达式。

Approach 7: Use Lambda Expressions Throughout Your Application

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

  重新考虑printPersonsWithPredicate看是否还有别的地方可以使用Lambda表达式:

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.

  这个方法检查List类型参数roster 中的每个Person 实例是否满足Predicate 类型接口tester规定的标准。如果Person 满足tester中指定的测试标准,Person中的printPersron方法将会被执行。

  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:

  替换printPerson方法,我们可以在Person实例中指定一个符合tester规定的标准的不同的要执行的操作。你可以用lambda表达式指定动作。加入你想要一个类似于printPerson的Lambda表达式,一个带有一个参数(Person类型对象)并且返回值为void。我们应当注意,在使用Lambda表达式,我们需要一个函数式接口。在这种情况下,你需要一个包含一个Person类型的参数并且返回值为void的抽象方法。Consumer<T>接口包含方法void accept(T t),满足需要的这些特征。下面我们使用Consumer<Person>来替换p.printPerson():

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:
  因此,下面的方法执行与当你调用并执行Approach 3: Specify Search Criteria Code in a Local ClassprintPersons 一样,获得有选择服务资格的会员。Lambda表达式用来打印成员:

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 parameter mapper, and then performs an action on it specified by the parameter block:

  如果你想在打印成员简介的时候打印更多内容该怎么办?假设你想要验证成员的简介的合法性或检索他们的联系方式该怎么办?在这种情况下,需要一个包含返回值的抽象方法的函数式接口。Function<T,R>接口包含方法R apply(T t)。下面的方法通过参数mapper检索数据,然后通过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 更广泛地使用泛型

  Reconsider the method processPersonsWithFunction. The following is a generic version of it that accepts, as a parameter, a collection that contains elements of any data type:

  重新考虑processPersonsWithFunction方法。 下面的泛型示例,接收一个包含任何数据类型的集合作为参数。

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方法如下:

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 Aggregate Operations That Accept Lambda Expressions as Parameters

  The following example uses aggregate operations to print the e-mail addresses of those members contained in the collection roster who are eligible for Selective Service:

  下面的示例使用聚合操作打印包含可选服务的集合列表中包含的成员的电子邮件地址:

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.

  在GUI应用程序中处理事件,例如键盘操作,鼠标操作和滚轮操作,通常创建事件处理程序,通常关联实现特定接口。通常事件处理接口是函数式接口;他们往往只有一个方法。
  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 typeEventHandler<ActionEvent>. The EventHandler<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一个Lambda表达式包括以下内容:

  • A comma-separated(逗号分隔) list of formal parameters enclosed(封闭形式参数) in parentheses(圆括号). The CheckPerson.test method contains one parameter, p, which represents(代表) an instance(实例) of the Personclass.

    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.

  注意,一个lambda表达式看起来像一个方法声明;你可以考虑lambda表达式作为匿名方法。
  The following example, Calculator, is an example of lambda expressions that take more than one formal parameter:

  下面的计算器例子中,lambda表达式接收多个形参:

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

Accessing Local Variables of the Enclosing Scope 访问封闭范围的局部变量

  Like local and anonymous classes, lambda expressions can capture variables; they have the same access to local variables of the enclosing scope. However, unlike local and anonymous classes, lambda expressions do not have anyshadowing issues (see Shadowing for more information). Lambda expressions are lexically scoped. This means that they do not inherit any names from a supertype or introducea new level of scoping. Declarations in a lambda expression are interpreted just as they are in the enclosing environment. The following example,LambdaScopeTest, demonstrates this:
  像本地和匿名函数,lambda表达式可以捕获变量;他们有权限访问封闭范围内的本地变量。然而,不同于本地和匿名类,lambda表达式没有任何跟踪问题 (see Shadowing for more information)。Lambda表达式是词法作用域。这意味着他们没有继承任何名字从父类型或引入一个新的水平域。在一个lambda表达式声明解释就像是在封闭的环境。下面的例子中,LambdaScopeTest,演示了这一点:

 

import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            
            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99;
            
            Consumer<Integer> myConsumer = (y) -> 
            {
                System.out.println("x = " + x); // Statement A
                System.out.println("y = " + y);
                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);
    }
}

This example generates the following output:

x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

If you substitute(代替) the parameter x in place of y in the declaration of the lambda expression myConsumer, then the compiler generates an error:

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

  The compiler generates the error "variable x is already defined in method methodInFirstLevel(int)" because the lambda expression does not introduce a new level of scoping. Consequently, you can directly access fields, methods, and local variables of the enclosing scope. For example, the lambda expression directly accesses the parameter x of the methodmethodInFirstLevel. To access variables in the enclosing class, use the keyword this. In this example, this.x refers to the member variable FirstLevel.x.

  However, like local and anonymous classes, a lambda expression can only access local variables and parameters of the enclosing block that are final or effectively final. For example, suppose that you add the following assignment statement immediately after the methodInFirstLevel definition statement:

void methodInFirstLevel(int x) {
    x = 99;
    // ...
}

Because of this assignment statement, the variable FirstLevel.x is not effectively final anymore. As a result, the Java compiler generates an error message similar to "local variables referenced from a lambda expression must be final or effectively final" where the lambda expression myConsumer tries to access the FirstLevel.x variable:

System.out.println("x = " + x);

Target Typing

How do you determine the type of a lambda expression? Recall the lambda expression that selected members who are male and between the ages 18 and 25 years:

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

This lambda expression was used in the following two methods:

When the Java runtime invokes the method printPersons, it's expecting a data type of CheckPerson, so the lambda expression is of this type. However, when the Java runtime invokes the method printPersonsWithPredicate, it's expecting a data type of Predicate<Person>, so the lambda expression is of this type. The data type that these methods expect is called the target type. To determine the type of a lambda expression, the Java compiler uses the target type of the context or situation in which the lambda expression was found. It follows that you can only use lambda expressions in situations in which the Java compiler can determine a target type:

  • Variable declarations

  • Assignments

  • Return statements

  • Array initializers

  • Method or constructor arguments

  • Lambda expression bodies

  • Conditional expressions, ?:

  • Cast expressions

Target Types and Method Arguments

For method arguments, the Java compiler determines the target type with two other language features: overload resolution and type argument inference.

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

public interface Runnable {
    void run();
}

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

The method Runnable.run does not return a value, whereas Callable<V>.call does.

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

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

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

Which method will be invoked in the following statement?

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

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

Serialization

You can serialize a lambda expression if its target type and its captured arguments are serializable. However, like inner classes, the serialization of lambda expressions is strongly discouraged.

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.Comparator;
import java.util.function.Predicate;
import java.lang.Iterable;
import java.time.chrono.IsoChronology;

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

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

        for (Person p : roster) {
            p.printPerson();
        }

        // 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

        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 4: Specify Search Criteria Code in an Anonymous Class

        System.out.println("Persons who are eligible for Selective Service " +
            "(anonymous class):");

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

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

赞赏

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/fanxiaobin577328725/article/details/54767551
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-25 01:56:42
  • 阅读 ( 1801 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢