Java Lambda 表达式的官网教程理解
Posted 顧棟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Lambda 表达式的官网教程理解相关的知识,希望对你有一定的参考价值。
文章目录
Lambda 表达式
匿名类的一个问题是,如果匿名类的实现非常简单,例如只包含一个方法的接口,那么匿名类的语法可能看起来笨拙且不清楚。 在这些情况下,您通常会尝试将功能作为参数传递给另一个方法,例如当有人单击按钮时应采取的操作。 Lambda 表达式使您能够做到这一点,将功能视为方法参数,或将代码视为数据。
匿名类 向您展示了如何在不给它命名的情况下实现基类。 虽然这通常比命名类更简洁,但对于只有一个方法的类,即使是匿名类也显得有些多余和繁琐。 Lambda 表达式让您可以更紧凑地表达单方法类的实例。
Lambda 表达式的语法
一个 lambda 表达式由以下内容组成:
- 括号中的形式参数的逗号分隔列表。
您可以省略 lambda 表达式中参数的数据类型。 此外,如果只有一个参数,您可以省略括号。(p1,p2)
- 箭头标记,
->
- 主体,由单个表达式或语句块组成。
在 lambda 表达式中,除了 void 方法,您必须将语句括在大括号 () 中。 但是,为了代码规范都需要保留大括号。return 语句不是表达式。可以通过return关键字显示的返回值。
请注意,lambda 表达式看起来很像方法声明。 您可以将 lambda 表达式视为匿名方法——没有名称的方法。
以下示例 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));
operateBinary
方法对两个整数操作数执行数学运算。 操作本身由IntegerMath
的一个实例指定。 该示例使用 lambda 表达式定义了两个操作,“加法”和“减法”。 该示例打印以下内容:
40 + 2 = 42
20 - 10 = 10
Lambda 表达式的理想用例
主要利用下方的示例,从“老式的常规实现”,“老式的通用实现”,“局部类的实现方式”,“匿名类的实现”,“部分lambda表达式”,“使用函数式接口的lambda表达式”,“接受 Lambda 表达式作为参数的聚合操作”一步步介绍lambda表达式的使用。
假设您正在创建一个社交网络应用程序。 您希望创建一个功能,使管理员能够对满足特定条件的社交网络应用程序的成员执行任何类型的操作,例如发送消息。 下表详细描述了此用例:
字段名 | 解释 |
---|---|
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 | Administrator specifies criteria of members on which to perform a certain action.Administrator specifies an action to perform on those selected members.Administrator selects the Submit button.The system finds all members that match the specified criteria.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. 1a。 管理员可以选择在指定要执行的操作之前或在选择提交按钮之前预览符合指定条件的成员。 |
Frequency of Occurrence | Many times during the day.一天中很多次。 |
假设此社交网络应用程序的成员由以下 Person
类表示:
public class Person
public enum Sex
MALE, FEMALE
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge()
// ...
public void printPerson()
// ...
假设您的社交网络应用程序的成员存储在List<Person>
实例中。
本节从对此用例的简单方法开始。 它使用局部类和匿名类改进了这种方法,然后使用 lambda 表达式以一种高效而简洁的方法结束。 在示例 RosterTest
中找到本节中描述的代码。
方法 1: 创建搜索匹配一个特征的成员的方法
一种简单的方法是创建几种方法; 每种方法都会搜索与某个特征(例如性别或年龄)相匹配的成员。 以下方法打印超过指定年龄的成员:
public static void printPersonsOlderThan(List<Person> roster, int age)
for (Person p : roster)
if (p.getAge() >= age)
p.printPerson();
注意:List
是有序的Collection
。 collection 是将多个元素组合成一个单元的对象。集合用于存储、检索、操作和通信聚合数据。有关集合的更多信息,请参阅 Collections 跟踪。
这种方法可能会使您的应用程序变得脆弱,这是由于引入更新(例如更新的数据类型)而导致应用程序无法运行的可能性。假设您升级应用程序并更改Person
类的结构,使其包含不同的成员变量;也许该类使用不同的数据类型或算法记录和测量年龄。您将不得不重写很多 API 以适应这种变化。此外,这种方法具有不必要的限制性;例如,如果您想打印小于某个年龄的成员怎么办?
方法 2: 创建更通用的搜索方法
下面的方法比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();
如果要打印指定性别的成员,或指定性别和年龄范围的组合,该怎么办? 如果您决定更改Person
类并添加其他属性(例如关系状态或地理位置)怎么办? 尽管此方法比 printPersonsOlderThan
更通用,但尝试为每个可能的搜索查询创建单独的方法仍然会导致代码脆弱。 相反,您可以将指定要在不同类中搜索的条件的代码分开。
方法 3: 在局部类中指定搜索条件代码
以下方法打印与您指定的搜索条件匹配的成员:
public static void printPersons(
List<Person> roster, CheckPerson tester)
for (Person p : roster)
if (tester.test(p))
p.printPerson();
此方法通过调用 tester.test
方法检查 List
参数 roster
中包含的每个 Person
实例是否满足 CheckPerson
参数 tester
中指定的搜索条件。 如果 tester.test
方法返回 true
值,则在 Person
实例上调用 printPersons
方法。
要指定搜索条件,您需要实现 CheckPerson
接口:
interface CheckPerson
boolean test(Person p);
下面的类通过指定方法 test
的实现来实现 CheckPerson
接口。 此方法过滤在美国有资格参加选择性服务的成员:如果其 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;
要使用这个类,你需要创建它的一个新实例并调用 printPersons
方法:
printPersons(
roster, new CheckPersonEligibleForSelectiveService());
尽管这种方法不那么脆弱——如果你改变了Person
的结构,你不必重写方法——你仍然有额外的代码:一个新的接口和一个局部类,用于你计划在应用程序中执行的每个搜索。 因为 CheckPersonEligibleForSelectiveService
实现了一个接口,所以您可以使用匿名类而不是局部类,并且无需为每次搜索声明一个新类。
方法 4: 在匿名类中指定搜索条件代码
以下调用方法printPersons
的参数之一是一个匿名类,它过滤在美国有资格参加选择性服务的成员:男性和年龄在 18 到 25 岁之间的成员:
printPersons(
roster,
new CheckPerson()
public boolean test(Person p)
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
);
这种方法减少了所需的代码量,因为您不必为要执行的每个搜索创建一个新类。 但是,考虑到 CheckPerson
接口只包含一个方法,匿名类的语法很庞大。 在这种情况下,您可以使用 lambda 表达式代替匿名类,如下一节所述。
方法 5: 使用 Lambda 表达式指定搜索条件代码
CheckPerson
接口是一个功能接口。 功能接口是仅包含一个 抽象方法 的任何接口。 (功能接口可能包含一个或多个 默认方法 或 [静态方法](https://docs.oracle. com/javase/tutorial/java/IandI/defaultmethods.html#static)。)因为函数式接口只包含一个抽象方法,所以在实现它时可以省略该方法的名称。 为此,您可以使用 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.
您可以使用标准功能接口代替接口CheckPerson
,这进一步减少了所需的代码量。
方法 6: 使用带有 Lambda 表达式的标准函数式接口
Reconsider the CheckPerson
interface:
重新考虑 CheckPerson
接口:
interface CheckPerson
boolean test(Person p);
这是一个非常简单的界面。 它是一个函数式接口,因为它只包含一个抽象方法。 此方法接受一个参数并返回一个boolean
值。 该方法非常简单,因此在您的应用程序中定义一个可能不值得。 因此,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)
:
JDK自带的标准函数式接口:
Consumer
Function
Predicate
Supplier
例如,您可以使用 Predicate<T>
接口代替 CheckPerson
。 该接口包含方法boolean test(T t)
:
interface Predicate<T>
boolean test(T t);
接口 Predicate<T>
是通用接口的一个示例。 (有关泛型的更多信息,请参阅 泛型(更新) 课程。)泛型类型(例如泛型接口)指定 尖括号 (<>
) 中的一个或多个类型参数。 这个接口只包含一个类型参数,T
。 当您使用实际类型参数声明或实例化泛型类型时,您就有了参数化类型。 例如,参数化类型 Predicate<Person>
如下:
interface Predicate<Person>
boolean test(Person t);
此参数化类型包含一个方法,该方法具有与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();
因此,下面的方法调用与您在[方法3:在本地类中指定搜索条件代码]中调用printPersons
时相同以获得有资格参加选择性服务的成员:
printPersonsWithPredicate(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
这不是此方法中唯一可能使用 lambda 表达式的地方。 以下方法建议使用 lambda 表达式的其他方法。
方法 7: 在整个应用程序中使用 Lambda 表达式
重新考虑方法 printPersonsWithPredicate
看看还有什么地方可以使用 lambda 表达式:
public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester)
for (Person p : roster)
if (tester.test(p))
p.printPerson();
此方法检查 List
参数 roster
中包含的每个 Person
实例是否满足 Predicate
参数 tester
中指定的条件。如果 Person
实例确实满足 tester
指定的条件,则在 Person
实例上调用方法 printPerson
。
除了调用 printPerson
方法,您可以指定不同的操作来对那些满足 tester
指定条件的 Person
实例执行。您可以使用 lambda 表达式指定此操作。假设你想要一个类似于 printPerson
的 lambda 表达式,它接受一个参数(Person
类型的对象)并返回 void。请记住,要使用 lambda 表达式,您需要实现一个函数式接口。在这种情况下,您需要一个函数式接口,其中包含一个抽象方法,该方法可以接受一个Person
类型的参数并返回 void。 Consumer<T>
接口包含方法 void accept(T t)
,它具有这些特性。以下方法将调用 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);
因此,下面的方法调用与您在[方法3:在本地类中指定搜索条件代码]中调用printPersons
时相同 以获得有资格参加选择性服务的成员。 用于打印成员的 lambda 表达式被突出显示:
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
指定的操作:
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
中包含有资格参加选择性服务的每个成员中检索电子邮件地址,然后将其打印出来:
processPersonsWithFunction(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
方法 8: 更广泛地使用泛型
重新考虑方法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);
要打印有资格参加选择性服务的成员的电子邮件地址,请调用 processElements
方法,如下所示:
processElements(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
此方法调用执行以下操作:
- 从集合
source
中获取对象的来源。在此示例中,它从集合roster
中获取Person
对象的来源。请注意,集合roster
是一个 List 类型的集合,也是一个Iterable
类型的对象。 - 过滤匹配
Predicate
对象tester
的对象。在此示例中,Predicate
对象是一个 lambda 表达式,用于指定哪些成员有资格获得选择性服务。 - 将每个过滤的对象映射到由
Function
对象mapper
指定的值。在这个例子中,Function
对象是一个返回成员电子邮件地址的 lambda 表达式。 - 对由
Consumer
对象block
指定的每个映射对象执行一个动作。在本例中,Consumer
对象是一个打印字符串的 lambda 表达式,该字符串是Function
对象返回的电子邮件地址。
您可以将这些操作中的每一个替换为聚合操作。
方法 9: 使用接受 Lambda 表达式作为参数的聚合操作
以下示例使用聚合操作来打印集合 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));
下表映射了方法 processElements
执行的每个操作与相应的聚合操作:
processElements Action | Aggregate Operation |
---|---|
获取对象的来源 | Stream<E> **stream**() |
过滤与Predicate 对象匹配的对象 | Stream<T> **filter**(Predicate<? super T> predicate) |
将对象映射到由 Function 对象指定的另一个值 | <R> Stream<R> **map**(Function<? super T,? extends R> mapper) |
执行 Consumer 对象指定的操作 | void **forEach**(Consumer<? super T> action) |
操作filter
、map
和forEach
是聚合操作。 聚合操作处理来自流的元素,而不是直接来自集合(这就是本例中调用的第一个方法是 stream
的原因)。 stream 是一系列元素。 与集合不同,它不是存储元素的数据结构。 相反,流通过管道从源(例如集合)携带值。 pipeline 是一系列流操作,在这个例子中是filter
-map
-forEach
。 此外,聚合操作通常接受 lambda 表达式作为参数,使您能够自定义它们的行为方式。
For a more thorough discussion of aggregate operations, see the Aggregate Operations lesson.
使用注意点
访问封闭作用域的局部变量
与局部类和匿名类一样,lambda 表达式可以捕获变量; 它们对封闭范围的局部变量具有相同的访问权限。 但是,与本地和匿名类不同,lambda 表达式没有任何阴影问题(有关详细信息,请参阅 Shadowing) . 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)
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以上是关于Java Lambda 表达式的官网教程理解的主要内容,如果未能解决你的问题,请参考以下文章