Java8 turial - Lambda 表达式(1)

来源:互联网 发布:淘宝如何登录店铺 编辑:程序博客网 时间:2024/05/22 05:32

进来Java8势头很猛,以前的知识面临再次更新。虽然在oracle已经一年多了,一直都没怎么好好地把英文文档啃好。看到oralce的文档都已经更新到Java8了,没理由不仔细阅读下。这个可比市面上一大批的书籍精良多了。废话不多说,每周都更新一篇,不过由于英文有限,有些词汇把握不准。大家就着英文一起看把。

这次先以lamda表达式开始,将原文和译文放在一起便于对照。 :)

=====================================================================================================

Lambda 表达式

匿名内部类的一个缺陷是当你的匿名内部类的实现很简单时,譬如是一个实现了只有一个方法的接口,这种语法就会显得笨重,不够清晰。在这种情况下,你会经常性地试图将一个功能的实现以参数的方式传递给一个方法,这个功能比如是一个点击了按钮将触发的事件动作。Lambda表达式能让你将功能作为一个方法的参数或像处理数据一样进行编程。
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.

在之前的章节中,Anonymous Classes, 展示了如何不通过显示地指定类名就能实现一个基础类的实现,尽管这经常比有命名的类更简单,但对于仅有一个方法的类,甚至是一个匿名内部类这种实现看起来有点过度累赘。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.


以下内容包含几个主题:
This section covers the following topics:

  • 使用Lamda表达式的理想实例
    • 方法1:创建通过某个特征能够搜索出成员的方法
    • 途径2:创建更通用的搜索方法
    • 途径3:在一个本地类中指定搜索条件
    • 方法4:在匿名内部类中指定搜索条件
    • 方法5:用lamda表达式指定搜索条件对应的代码
    • 方法6:共同使用lamda表达式和标准的函数式接口
    • 方法7:使用lamba表达式遍及你的应用
    • 方法8:更广泛地使用泛型
    • 方法9:使用聚合操作接受多个lamda表达式做参数
  • GUI应用中的lambda表达式
  • Lamda表达式的语法
  • 访问作用域中的局部变量
  • Target Typing
    • 目标类型和方法参数
  • 序列化
  • Ideal Use Case for Lambda Expressions
    • Approach 1: Create Methods That Search for Members That Match One Characteristic
    • Approach 2: Create More Generalized Search Methods
    • 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
    • Approach 6: Use Standard Functional Interfaces with Lambda Expressions
    • Approach 7: Use Lambda Expressions Throughout Your Application
    • Approach 8: Use Generics More Extensively
    • Approach 9: Use Aggregate Operations That Accept Lambda Expressions as Parameters
  • Lambda Expressions in GUI Applications
  • Syntax of Lambda Expressions
  • Accessing Local Variables of the Enclosing Scope
  • Target Typing
    • Target Types and Method Arguments
  • Serialization

使用Lamda表达式的理想实例

Ideal Use Case for Lambda Expressions

假如你正在实现一个社交网络的应用,你想提供一个特性能够让管理员执行某种动作,比如发送一条消息给社交网络应用中的根据特定判断条件过滤出的某些成员。下面的这个表格描述了这个用例的细节:
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:

FieldDescription用例名称对所选人员执行动作主要角色管理员前置条件管理员已登陆系统后置条件只对满足特定条件的成员执行该动作 成功执行的主场景
  1. 管理员指定条件过滤出需要进行执行特定动作的人员
  2. 管理员对入选的人员指定需要执行的动作
  3. 管理员点击提交按钮
  4. 系统根据条件查找所有符合条件的人员
  5. 系统对选中的人员执行动作
扩展项(备选事件)

1a. 管理员在指定要动作或点击提交前,能够通过某个选项预览到符合命中条件的人员。

出现频率每天多次

FieldDescriptionNamePerform action on selected membersPrimary ActorAdministratorPreconditionsAdministrator is logged in to the system.PostconditionsAction 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 OccurrenceMany times during the day.

假设社交网络应用中的人员用Person类表示:
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() {        // ...    }}

假设社交网络中的人员保存在一个List<Person>实例中:
Suppose that the members of your social networking application are stored in a List<Person> instance.

这部分内容先用一个比较幼稚的方法去实现这个用例。先使用局部匿名类来改进,之后再使用一个高效的、简洁的lambda 表达式结束示例。你可以在RosterTest例子中查找到描述这部分内容的代码片段

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.


  • 方法1:创建通过某个特征能够搜索出成员的方法

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


这个方法会使你的应用潜在地变得脆弱,很有可能会导致应用无法正常运作,导致无法正常运行的可能会源于修改引入(比如数据类型变更了),假设你升级你的应用,并且改变了Person类的结构使其包含了不同的成员变量;也许这个变更后的类使用了一个不同的数据类型或算法来记录并计算年龄。为了兼容这个变更,你将不得不去重写很多API。此外,这个方法是没有必要进行限制的,如果你想打印出比某个年龄更年轻的成员将会怎样,例如?
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?

途径2:创建更通用的搜索方法

Approach 2: Create More Generalized Search Methods


下面这个方法比printPersonsOlderThan更通用些;它打印出年龄在某个范围内的成员信息;
The following method is more generic than printPersonsOlderThan; it prints members within a specified range of ages:

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

如果你想打印出性别明确,或者组合性别和年龄属性的成员,又会怎样?如果你决定修改Person类并且加上其他的属性比如婚姻状况或地理位置,又会怎样?尽管这个方法比printPersonsOlderThan更通用,但试图为每个可能的搜索查询都创建一个独立的方法仍然会导致代码脆弱。你可以在一个其他的类中实现,而不是在这个类中来用不同的方法来区分不同条件的实现。
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.

途径3:在一个本地类中指定搜索条件

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

这个方法检测List类型参数roster中的每个Person实例,通过调用tester.test方法去判断是否满足定义在CheckPerson类型参数tester中的条件。如果tester.test方法返回一个true值,Peson实例中的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.

为了指定搜索条件,你实现了CheckPerson接口:
To specify the search criteria, you implement the CheckPerson interface:

interface CheckPerson {    boolean test(Person p);}

下面这个类实现了CheckPerson接口并提供了test方法的实现。这个方法过滤在美国符合服兵役条件的人员:如果检测到这个人是男性且年龄在18到25岁之间,那这个方法将返回true值。
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 atrue value if its Person parameter is male and between the ages of 18 and 25:

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

为了使用这个类,你创建了一个对应的实例并且调用其printPerson方法
To use this class, you create a new instance of it and invoke the printPersons method:

printPersons(    roster, new CheckPersonEligibleForSelectiveService());

尽管这个方法有一点点脆弱——当你更改Person类结构时无需重写该方法——但你仍然需要额外的代码:为了去执行应用中的搜索条件你得创建一个新的接口,新的局部实现例。但得益于CheckPersonEligibleForSelectiveService 实现了这个接口,你可以使用匿名内部类来替代局部类的实现方式,而且你无需为每一个搜索条件都定义一个新的类。
Although this approach is less britlte—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.

方法4:在匿名内部类中指定搜索条件

Approach 4: Specify Search Criteria Code in an Anonymous Class


下面的这个printPersons 调用方法包含一个匿名内部类的参数,它可以过滤出美国符合服兵役的人员:这些人都是男性并且年龄在18至25岁之间。
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;        }    });

这个方法缩减了一些必须的代码,因为你无需为你想要执行搜索的条件再创建一个新的类。尽管如此,匿名内部类的语法是被认为笨重的,因为CheckPerson 接口只有一个方法。在这种场景下,你可以使用下面介绍到的lambda 表达式来替代匿名内部类。
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 theCheckPerson 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.

方法5:用lamda表达式指定搜索条件对应的执行代码

Approach 5: Specify Search Criteria Code with a Lambda Expression


CheckPerson 接口是一个函数式的接口。任何只包含一个抽象方法的接口称为一个函数式接口。(一个函数式接口也许包含一个或多个default方法或static方法)因为函数式接口只有一个抽象方法,当你实现时,你可以省去这个方法的名称。为了做到这点,你可以使用lamda表达式而不是匿名内部类表达式,在下面这个方法中高亮标示出lamda表达式的实现:
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 alambda expression, which is highlighted in the following method invocation:

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

关于如何定义一个lamda表达式的细节,你可以参与lamba表达式语法章节
See Syntax of Lambda Expressions for information about how to define lambda expressions.


你也可以使用一个标准的功能性接口来取代CheckPerson接口,这也可以进一步地缩减所必须的一定数量的代码
You can use a standard functional interface in place of the interface CheckPerson, which reduces even further the amount of code required.

方法6:一起使用lamda表达式和标准的函数式接口

Approach 6: Use Standard Functional Interfaces with Lambda Expressions


考虑这个CheckPerson接口:
Reconsider the CheckPerson interface:

interface CheckPerson {    boolean test(Person p);}

这是一个非常简单的接口。它是一个函数式接口因为它只有一个抽象方法。并且这个方法有一个参数并且返回一个boolean值。而且这个方法简单得也许不值得在你的应用中定义。于是,JDK定义了几个标准的函数式接口,你可以在java.util.function包中找到它们。
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.


举个例子,你可以使用Predicate<T>接口(判断接口)来替代CheckPerson。这个接口包含了 boolean test(T t)方法:
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);}

这个接口是一个泛型接口的例子。(更多关于泛型的信息,参阅泛型教程)泛型(例如泛型接口)在一对尖角括号中指定一个或多个参数类型。这个接口中只包含了一个类型 T.
当你用一个具体类型的参数来定义或举例说明一个泛化类型时,你就会有一个参数化的类型。例如,参数化的类型Predicate<Person> 如下所示:
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);}
//todo--
这个参数化的类型包含了一个方法,这个方法和 CheckPerson.boolean test(Person p)一样,具有相同的返回类型和参数,你可以使用Predicate<T> 替代CheckPerson,如下所示 :
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:

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

结果,对下面这个方法的调用跟调用printPersons 一样,可以获得符合服兵役的人员:
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);

在这个方法中,这不是唯一可以使用lamda表达式的地方。下面的这个方法建议了用其他方式去使用lamda表达式
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.

方法7:在你的应用中广泛地使用lamba表达式

Approach 7: Use Lambda Expressions Throughout Your Application


重新考虑下方法printPersonsWithPredicate ,再看下是否还有别的什么地方可以使用lamda表达式
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();        }    }}

这个方法检测List型参roster中的每个Person实例,通过调用tester.test方法去判断是否满足定义在Predicate tester参数中的条件。如果tester.test方法返回一个true值,Peson实例中的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.


为了不调用printPerson方法,你可以指定不同的动作在这些Peson实例上执行,以匹配tester规定的搜索条件。你可以用lamda表达式来指定这个动作。假设你需要一个跟printPerson方法功能相近的lamda表达式,只需要一个参数(是一个Person类型的对象)并且返回类型为void.记住,要使用一个lamda表达式,你需要实现一个函数式接口。在这种情况下,你需要一个函数式接口,这个接口里面包含了一个抽象方法,这个方法可以接受一个Person类型的对象参数并且返回类型为void。Consumer<T>接口包含了一个方法void accept(T t),这个方法具备之前的一系列要求。下面的这个方法,使用一个Consumer<Person>的实例来调用accept方法的方式来取代对p.printPerson()方法的调用。
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);            }        }}

结果,对下面这个方法的调用跟printPersons方法的调用一样可以获得服兵役的人员。下面用lamda表达式打印印其中的人员并且高亮显示。
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());

假如你想根据人员的属性做更多的事而不是只打印出人员信息,譬如是你想要校验人员的属性或者获取他们的联系方式?在这种场景下,你需要一个函数式的接口,其包含一个有返回值的抽象方法。Function<T,R>这个接口包含了一个方法R apply(T t). 下面的这个方法获取由参数mapper规定的数据,并且之后执行一个由参数block指定的动作。
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:

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

下面这个方法获取roster中,适合服兵役的人员email地址,之后再打印出来:
The following method retrieves the email address from each member contained in roster who are 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));

 方法8:更广泛地使用泛型

Approach 8: Use Generics More Extensively


重新考虑processPersonsWithFunction方法,下面是一个泛型版本的实现
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:

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

要打印出适合服兵役人员的email地址,就可以如下所示调用processElements 方法:
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. 从集合source对象中获取一个源对象,它从集合roster对象中获取一个Person的对象。注意collection roster 是一个实现List接口的集合类型,也是一个实现了Iterable接口的对象类型
  2. 过滤出满足Predicate 类的tester对象,在这个示例中,Predicate 对象是一个lamda表达式,其规定了哪些人员适合服兵役。
  3. 将过滤出的每个对象都映射成由Function 对象mapper规定的一个值.在这个示例中,Function对象是一个可以返回人员email地址的lamda表达式
  4. 在每个映射对象上执行由Consumer类型的对象block规定的动作。在这个例子中,Consumer对象是一个lamda表达式,打印一个字符串,实际上是由Function对象返回的email地址
  5. 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 typeList, is also an object of type Iterable.
  6. 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.
  7. 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.
  8. 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.

方法9:使用聚合操作接受多个lamda表达式作参数

Approach 9: Use Aggregate Operations That Accept Lambda Expressions as Parameters


下面这个例子使用聚合操作去打印集合roster中的人员email地址(适合服兵役的人员): 
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));

下面这个表格映射了processElements 方法执行的每个操作,processElements 方法伴有相应的聚合操作
The following table maps each of the operations the method processElements performs with the corresponding aggregate operation:

processElements ActionAggregate Operation获取对象的一个来源
Obtain a source of objects
Stream<E> stream()过滤出符合Predicate 判断对象的对象
Filter objects that match a Predicate object
Stream<T> filter(Predicate<? super T> predicate)将由Function规定的对象映射成一个其他的对象
Map objects to another value as specified by a Function object
<R> Stream<R> map(Function<? super T,? extends R> mapper)执行由Consumer对象规定的动作
Perform an action as specified by a Consumer object
void forEach(Consumer<? super T> action)

filter,map,foreach 都是属于聚合操作方法。聚合操作处理的是stream中的元素,而不是不直接操作collection(这是为什么在这个例子中,第一个被调用的方法是stream的原因)。一个stream是一个元素的序列,与collection不同,它不是用于保存元素的数据结构。相反,一个stream运载着了源对象的值,就如同collection通过一个管道一样(的道理)。管道是一系列的stream操作,在这个例子中就是filtermap-forEach。此外,聚合操作典型地接受lamda表达式作为参数,使你能够自行定义他们的行为。
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. Apipeline 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.

GUI应用中的lambda表达式

Lambda Expressions in GUI Applications

要处理GUI应用中的事件,比如键盘事件,鼠标事件,滚动事件,典型地你会创建实现了一个特定接口的事件处理类。事件处理接口经常是倾向于只有一个方法的函数式接口。

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.


在JavaFx例子中HelloWorld.java(在之前的匿名内部类章节讨论过),你可以将下面标亮的匿名内部类用一个lamda表达式替换:
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!");            }        });

这个方法调用btn.setOnAction 

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

Lamda表达式的语法

Syntax of Lambda Expressions


一个lamda表达式由下面这些组成
A lambda expression consists of the following:

  • 圆括号中的一组以逗号分割的型参。CheckPerson.test方法包含一个代表Person的实例的参数P。
    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.

    注意:你可以省去lamda表达式中的参数类型,此外,如果只有一个参数的情况下你也可以省去圆括号( )。例如,下面这个lamda表达式也是有效的:
    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, ->
  • 一个lamda表达式的代码体,由一个单一的表达式或代码块组成,这个例子使用了下面的表达式:
    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

    如果你指定了一个单一的表达式,在java运行期会计算并返回表达式的结果。还有另一种选择,你可以使用一个具有返回值的代码片段。
    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;}

    具有返回值的代码段不是一个表达式,在lamda表达式中你必须使用花括号({})来包住代码段。尽管如此,你没有必要使用花括号({})来包住一个只有void返回的方法调用,例如下面这个是一个有效的lamda表达式:
    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)

注意lamda表达式看上去非常像是一个方法的定义;你可以认为lamda表达式是一个匿名的方法——没有名字的方法
Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.

下面这个例子,Calculator,是一个有多个参数的lamda表达式的例子:
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));        }}

operateBinary 方法在两个整型值上执行数学运算。运算操作是由一个IntegerMath的实例指定的。这个例子用lamda表达式定义了加法和减法这两个操作。这个例子打印结果如下:
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 = 4220 - 10 = 10

访问作用域中的局部变量

Accessing Local Variables of the Enclosing Scope


像局部匿名类一样,lamda表达式可以捕获变量,它们都有相同的通道访问到作用域中的变量。尽管如此,跟匿名局部类不同的是,lamda表达式没有任何“shadowing” 缺陷。lamda表达式是常量作用域的,这意味这不继承任何来自父类的名称或者一个新级别的作用域。lamda表达式中的声明是解释执行的犹如在一个封闭环境中。下面这个例子LambdaScopeTest,演示了这个:
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 any shadowing issues (see Shadowing for more information). Lambda expressions are lexically scoped. This means that they do not inherit any names from a supertype or introduce a 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:

import java.util.function.Consumer;public class LambdaScopeTest {    public int x = 0;    class FirstLevel {        public int x = 1;        void methodInFirstLevel(int x) {            
            //下面这个语句会导致编译器产生错误“本地变量已被一个lamda表达式引用
            // 必须是final或者实际上是final类型
            // 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 = 23y = 23this.x = 1LambdaScopeTest.this.x = 0

如果你用x取代lamda表达式 myConsumer定义中的y ,之后编译器会产生错误:
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) -> {    // ...}

编译器产生这个error :"变量x已经在methodInFirstLevel(int) 方法中定义", 由于编译器不会引入一个新级别的作用域。因此你可以直接访问属性,方法和作用域中的局部变量。例如,lamda表达式直接访问methodInFirstLevel方法中的参数x,要访问内部类的变量,需要使用this关键字。在这个例子中,this.x引用的是成员变量FirstLevel.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 method methodInFirstLevel. To access variables in the enclosing class, use the keyword this. In this example, this.x refers to the member variable FirstLevel.x.

然而,跟本地匿名内部类一样,一个lamda表达式只能访问内部块的final或实际上是final的本地变量和参数。例如,假设你在methodInFirstLevel 定义语句之后立即添加了下面的这个任务语句:
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;    // ...}

因为这个任务语句,变量FirstLevel.x不再是一个有效的final类型。结果在lamda表达式myConsumer试图访问 FirstLevel.x的地方,java编译器会产生一个错误信息,类似“本地变量已被一个lamda表达式引用必须是final或者实际上是final类型
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

你是如何确定lamda表达式的类型的?回想下面用来筛选出性别是男性且年龄在18至25岁人员的lamda表达式:
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

这个lamda表达式被下面两个方法使用:
This lambda expression was used in the following two methods:

  • public static void printPersons(List<Person> roster, CheckPerson tester) in Approach 3: Specify Search Criteria Code in a Local Class

  • public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) in Approach 6: Use Standard Functional Interfaces with Lambda Expressions


当Java运行期调用printPersons方法时,会期望获知CheckPerson的数据类型,因此lamda表达式是与此相关的一个类型。然而,当java运行期调用方法printPersonsWithPredicate时,会期望获得Predicate<Person>的数据类型,因此lamda表达式是与这相关的一个类型。这些方法想获知的类型即称为目标类型。为了判断lamda表达式的类型,Java编译器使用能够发现lamda表达式的上下文或场景的目标类型。它遵循这样的原则,你只能使用Java编译器能够判断出目标类型的lamda表达式:
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 methodprintPersonsWithPredicate, 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
  • lamda表达式体
    Lambda expression bodies
  • 条件表达式,?:
    Conditional expressions, ?:
  • 强制转换表达式
    Cast expressions

目标类型和方法参数

Target Types and Method Arguments


关于方法参数,Java编译器使用两种语言特性去判断它的目标类型:重载解析和类型参数接口
For method arguments, the Java compiler determines the target type with two other language features: overload resolution and type argument inference.

考虑下面的两个函数接口( java.lang.Runnable 和java.util.concurrent.Callable<V>
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();}

Runnable.run方法不返回任何值,但Callable<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");

invoke(Callable<T>)方法将会被调用,因为这个方法有返回值;在这种情况下lamda表达式的类型 () -> "done" 是Callable<T>类型。
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" isCallable<T>.

0 0
原创粉丝点击