Documentation

The Java™ Tutorials
Hide TOC
Lambda ExpressionsLambda表达式
Trail: Learning the Java Language
Lesson: Classes and Objects
Section: Nested Classes

Lambda ExpressionsLambda表达式

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表达式使您能够更简洁地表达单个方法类的实例。

This section covers the following topics:本节涵盖以下主题:

Ideal Use Case for Lambda ExpressionsLambda表达式的理想用例

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:假设此社交网络应用程序的成员由以下Person类表示:

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.它使用本地类和匿名类改进了这种方法,然后使用lambda表达式以一种高效简洁的方法结束。Find the code excerpts described in this section in the example RosterTest.在示例RosterTest中查找本节中描述的代码摘录。

Approach 1: Create Methods That Search for Members That Match One Characteristic方法1:创建搜索匹配一个特征的成员的方法

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.List是一个有序的CollectionA 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 the 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.假设升级应用程序并更改Person类的结构,使其包含不同的成员变量;也许类使用不同的数据类型或算法记录和测量年龄。You would have to rewrite a lot of your API to accommodate this change.您必须重写大量API以适应此更改。In addition, this approach is unnecessarily restrictive; what if you wanted to print members younger than a certain age, for example?此外,这种方法具有不必要的限制性;例如,如果要打印小于某个年龄的成员,该怎么办?

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

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?如果您决定更改Person类并添加其他属性,如关系状态或地理位置,该怎么办?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.尽管此方法比printPersonsOlderThan更通用,但尝试为每个可能的搜索查询创建单独的方法仍然会导致代码脆弱。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方法3:在本地类中指定搜索条件代码

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.此方法通过调用tester.test方法检查List参数roster中包含的每个Person实例是否满足CheckPerson参数tester中指定的搜索条件。If the method tester.test returns a true value, then the method printPersons is invoked on the Person instance.如果方法tester.test返回一个true值,则在Person实例上调用方法printPersons

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.下面的类通过指定方法test的实现来实现CheckPerson接口。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:此方法筛选符合美国选择性服务条件的成员:如果其Person参数为男性且年龄在18到25岁之间,则返回true值:

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

To use this class, you 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如果你改变了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.因为CheckPersonEligibleForSelectiveService实现了一个接口,所以您可以使用匿名类而不是本地类,并且不需要为每次搜索声明新类。

Approach 4: Specify Search Criteria Code in an Anonymous Class方法4:在匿名类中指定搜索条件代码

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.但是,考虑到CheckPerson接口只包含一个方法,匿名类的语法非常庞大。In this case, you can use a lambda expression instead of an anonymous class, as described in the next section.在这种情况下,可以使用lambda表达式而不是匿名类,如下一节所述。

Approach 5: Specify Search Criteria Code with a Lambda Expression方法5:使用Lambda表达式指定搜索条件代码

The CheckPerson interface is a functional interface.CheckPerson接口是一个功能接口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:为此,您可以使用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方法6:对Lambda表达式使用标准函数接口

Reconsider the CheckPerson interface:重新考虑CheckPerson接口:

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.此方法接受一个参数并返回一个boolean值。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.例如,您可以使用Predicate<T>接口代替CheckPersonThis interface contains the method boolean test(T t):此接口包含boolean test(T t)方法:

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

The interface Predicate<T> is an example of a generic interface.接口Predicate<T>是通用接口的一个示例。(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.此接口仅包含一个类型参数TWhen 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<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).此参数化类型包含与CheckPerson.boolean test(Person p)具有相同返回类型和参数的方法。Consequently, you can use Predicate<T> in place of CheckPerson as the following method demonstrates:因此,您可以使用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:因此,以下方法调用与在方法3:在本地类中指定搜索条件代码中调用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.这不是此方法中使用lambda表达式的唯一可能位置。The following approach suggests other ways to use lambda expressions.下面的方法建议使用lambda表达式的其他方法。

Approach 7: Use Lambda Expressions Throughout Your Application方法7:在整个应用程序中使用Lambda表达式

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.此方法检查List参数roster中包含的每个Person实例是否满足Predicate参数tester中指定的条件。If the Person instance does satisfy the criteria specified by tester, the method printPerson is invoked on the Person instance.如果Person实例确实满足tester指定的条件,则在Person实例上调用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.您可以指定一个不同的操作来对满足tester指定的条件的Person实例执行,而不是调用printPerson方法。You can specify this action with a lambda expression.可以使用lambda表达式指定此操作。Suppose you want a lambda expression similar to printPerson, one that takes one argument (an object of type Person) and returns void.假设您想要一个类似于printPerson的lambda表达式,该表达式接受一个参数(Person类型的对象)并返回voidRemember, to use a lambda expression, you need to implement a functional interface.记住,要使用lambda表达式,需要实现一个函数接口。In this case, you need a functional interface that contains an abstract method that can take one argument of type Person and returns void.在本例中,您需要一个函数接口,该接口包含一个抽象方法,该方法可以接受Person类型的一个参数并返回voidThe Consumer<T> interface contains the method void accept(T t), which has these characteristics.Consumer<T>接口包含具有以下特征的void accept(T t)方法。The following method replaces the invocation p.printPerson() with an instance of Consumer<Person> that invokes the method accept:以下方法将调用p.printPerson()替换为调用accept方法的Consumer<Person>的实例:

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.因此,以下方法调用与在方法3:在局部类中指定搜索条件代码以获取符合选择性服务条件的成员中调用PrintPerson时相同。The lambda expression used to print members is highlighted:用于打印成员的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).Function<T,R>接口包含方法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:以下方法检索参数mapper指定的数据,然后对参数块指定的数据执行操作:

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:以下方法从roster中包含的每个有资格获得选择性服务的成员处检索电子邮件地址,然后打印该地址:

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方法8:更广泛地使用泛型

Reconsider the method processPersonsWithFunction.重新考虑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);
        }
    }
}

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.从集合source获取对象的源。In this example, it obtains a source of Person objects from the collection roster.在本例中,它从集合roster中获取Person对象的源。Notice that the collection roster, which is a collection of type List, is also an object of type Iterable.请注意,集合rosterList类型的集合,也是Iterable类型的对象。
  2. Filters objects that match the Predicate object tester.筛选与Predicate对象tester匹配的对象。In this example, the Predicate object is a lambda expression that specifies which members would be eligible for Selective Service.在本例中,Predicate对象是一个lambda表达式,指定哪些成员有资格获得选择性服务。
  3. Maps each filtered object to a value as specified by the Function object mapper.将每个筛选对象映射到Function对象mapper指定的值。In this example, the Function object is a lambda expression that returns the e-mail address of a member.在本例中,Function对象是一个lambda表达式,返回成员的电子邮件地址。
  4. Performs an action on each mapped object as specified by the Consumer object block.按照Consumer对象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.在本例中,Consumer对象是一个lambda表达式,它打印一个字符串,该字符串是Function对象返回的电子邮件地址。

You can replace each of these actions with an aggregate operation.您可以用聚合操作替换这些操作中的每一个。

Approach 9: Use Aggregate Operations That Accept Lambda Expressions as Parameters方法9:使用接受Lambda表达式作为参数的聚合操作

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中包含的成员的电子邮件地址:

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方法执行的每个操作与相应的聚合操作:

processElements Action操作 Aggregate Operation聚合操作
Obtain a source of objects获取对象的来源 Stream<E> stream()
Filter objects that match a Predicate object筛选与Predicate对象匹配的对象 Stream<T> filter(Predicate<? super T> predicate)
Map objects to another value as specified by a Function object将对象映射到Function对象指定的另一个值 <R> Stream<R> map(Function<? super T,? extends R> mapper)
Perform an action as specified by a Consumer object执行Consumer对象指定的操作 void forEach(Consumer<? super T> action)

The operations filter, map, and forEach are aggregate operations.操作filtermapforEach是聚合操作。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).聚合操作处理来自流的元素,而不是直接来自集合的元素(这就是为什么在本例中调用的第一个方法是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 filter- map-forEach.管道是一系列流操作,在本例中是filter-map-forEachIn addition, aggregate operations typically accept lambda expressions as parameters, enabling you to customize how they behave.此外,聚合操作通常接受lambda表达式作为参数,使您能够自定义它们的行为方式。

For a more thorough discussion of aggregate operations, see the Aggregate Operations lesson.有关聚合操作的更深入的讨论,请参阅聚合操作课程。

Lambda Expressions in GUI ApplicationsGUI应用程序中的Lambda表达式

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.要处理图形用户界面(GUI)应用程序中的事件,例如键盘动作、鼠标动作和滚动动作,通常需要创建事件处理程序,这通常涉及实现特定的界面。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:在JavaFX示例HelloWorld.java(在前面的匿名类部分中讨论)中,您可以使用以下语句中的lambda表达式替换高亮显示的匿名类:

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.方法调用btn.setOnAction指定当您选择由btn对象表示的按钮时会发生什么。This method requires an object of type EventHandler<ActionEvent>.此方法需要EventHandler<ActionEvent>类型的对象。The EventHandler<ActionEvent> interface contains only one method, void handle(T event). EventHandler<ActionEvent>接口只包含一个方法,void handle(T event)This interface is a functional interface, so you could use the following highlighted lambda expression to replace it:此接口是一个函数接口,因此可以使用以下高亮显示的lambda表达式替换它:

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

Syntax of Lambda ExpressionsLambda表达式的语法

A lambda expression consists of the following:lambda表达式由以下内容组成:

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:以下示例Calculator是采用多个形式参数的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.operateBinary方法对两个整数操作数执行数学运算。The operation itself is specified by an instance of IntegerMath.操作本身由IntegerMath的实例指定。The example defines two operations with lambda expressions, addition and subtraction.该示例使用lambda表达式定义了两个操作:additionsubtractionThe 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.与本地类和匿名类一样,lambda表达式可以捕获变量;它们对封闭范围的局部变量具有相同的访问权限。However, unlike local and anonymous classes, lambda expressions do not have any shadowing issues (see Shadowing for more information).但是,与本地类和匿名类不同,lambda表达式没有任何阴影问题(有关更多信息,请参阅阴影)。Lambda expressions are lexically scoped.Lambda表达式在词汇范围内。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.lambda表达式中的声明与封闭环境中的声明一样进行解释。The following example, LambdaScopeTest, demonstrates this:以下示例LambdaScopeTest演示了这一点:

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

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

This example generates the following output:此示例生成以下输出:

x = 23
y = 23
z = 2
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:如果在lambda表达式myConsumer的声明中用参数x代替y,则编译器将生成错误:

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

The compiler generates the error "Lambda expression's parameter x cannot redeclare another local variable defined in an enclosing scope" because the lambda expression does not introduce a new level of scoping.编译器生成错误“Lambda表达式的参数x无法重新声明封闭范围中定义的另一个局部变量”,因为Lambda表达式未引入新级别的作用域。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.例如,lambda表达式直接访问methodInFirstLevel方法的参数xTo access variables in the enclosing class, use the keyword this.要访问封闭类中的变量,请使用关键字thisIn this example, this.x refers to the member variable FirstLevel.x.在本例中,this.x引用成员变量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.但是,与局部类和匿名类一样,lambda表达式只能访问封闭块的局部变量和参数,这些变量和参数是最终的或实际上是最终的。In this example, the variable z is effectively final; its value is never changed after it's initialized.在这个例子中,变量z实际上是最终的;它的值在初始化后不会更改。However, suppose that you add the following assignment statement in the the lambda expression myConsumer:但是,假设在lambda表达式myConsumer中添加以下赋值语句:

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

Because of this assignment statement, the variable z is not effectively final anymore.由于这个赋值语句,变量z实际上不再是最终。As a result, the Java compiler generates an error message similar to "Local variable z defined in an enclosing scope must be final or effectively final".结果,Java编译器生成类似于“在封闭范围内定义的局部变量z必须是final或实际上是final”的错误消息。

Target Typing目标类型

How do you determine the type of a lambda expression?如何确定lambda表达式的类型?Recall the lambda expression that selected members who are male and between the ages 18 and 25 years:回忆一下lambda的表达方式,即选择18至25岁的男性成员:

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

This lambda expression was used in the following two methods:此lambda表达式用于以下两种方法:

When the Java runtime invokes the method printPersons, it's expecting a data type of CheckPerson, so the lambda expression is of this type.当Java运行时调用printPerson方法时,它希望数据类型为CheckPerson,因此lambda表达式属于这种类型。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.但是,当Java运行时调用printPersonsWithPredicate方法时,它需要的数据类型是Predicate<Person>,所以lambda表达式就是这种类型的。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.为了确定lambda表达式的类型,Java编译器使用找到lambda表达式的上下文或情况的目标类型。It follows that you can only use lambda expressions in situations in which the Java compiler can determine a target type:因此,您只能在Java编译器可以确定目标类型的情况下使用lambda表达式:

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.对于方法参数,Java编译器使用另外两个语言特性确定目标类型:重载解析和类型参数推断。

Consider the following two functional interfaces (java.lang.Runnable and java.util.concurrent.Callable<V>):考虑下面两个函数接口(java.lang.Runnablejava.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.方法Runnable.run不返回值,而Callable<V>.call返回值。

Suppose that you have overloaded the method invoke as follows (see Defining Methods for more information about overloading methods):假设您已按如下方式重载了invoke方法(有关重载方法的更多信息,请参阅定义方法):

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.将调用方法invoke(Callable<T>),因为该方法返回一个值;而方法invoke(Runnable)不返回值。In this case, the type of the lambda expression () -> "done" is Callable<T>.在本例中,lambda表达式() -> "done"Callable<T>

Serialization系列化

You can serialize a lambda expression if its target type and its captured arguments are serializable.如果lambda表达式的目标类型及其捕获的参数可序列化,则可以序列化该表达式。However, like inner classes, the serialization of lambda expressions is strongly discouraged.但是,与内部类一样,强烈反对lambda表达式的序列化。


Previous page: Anonymous Classes
Next page: Method References