您的位置:js12345金沙官网登入 > 网络编程 > 【金沙澳门娱乐网址】深入理解Java 8 Lambda表达式

【金沙澳门娱乐网址】深入理解Java 8 Lambda表达式

2019-10-07 08:50

Java 8 问世三年了,9马上也要问世了,所以,嗯,我要开始学8了……

匿名内部类的一个问题是:当一个匿名内部类的实现非常简单,比如说接口只有一个函数,那么匿名内部类的语法有点笨拙且不清晰。我们经常会有传递一个函数作为参数给另一个函数的实际需求,比如当点击一个按钮时,我们需要给按钮对象设置按钮响应函数。lambda表达式就可以把函数当做函数的参数,代码(函数)当做数据(形参),这种特性满足上述需求。当要实现只有一个抽象函数的接口时,使用lambda表达式能够更灵活。

官方文档:http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

使用Lambda表达式的一个用例

假设你正在创建一个社交网络应用。你现在要开发一个可以让管理员对用户做各种操作的功能,比如搜索、打印、获取邮件等操作。假设社交网络应用的用户都通过Person类表示:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    private String name;

    private LocalDate birthday;

    private Sex gender;

    private String emailAddress;

    public int getAge() {
        // ...
    }

    public void printPerson() {
        // ...
    }
}

假设社交网络应用的所有用户都保存在一个 List<Person>的实例中。

我们先使用一个简单的方法来实现这个用例,再通过使用本地类、匿名内部类实现,最终通过lambda表达式做一个高效且简洁的实现。

**只是代码简洁了这个好处的话,并不能打动很多观众,java 8也不会这么令人期待,其实java 8引入lambda迫切需求是因为lambda 表达式能简化集合上数据的多线程或者多核的处理,提供更快的集合处理速度 **

匿名类的问题是,如果您的匿名类的实现非常简单,例如仅包含一个方法的接口,则匿名类的语法可能看起来很笨重且不清楚。在这些情况下,您通常会尝试将功能作为参数传递给另一种方法,例如当有人点击按钮时应该采取什么措施。Lambda表达式使您能够执行此操作,将功能视为方法参数或代码作为数据。

  • Lambda表达式的理想用例
  • 方法1:创建搜索匹配一个特征的成员的方法
  • 方法2:创建更广泛的搜索方法
  • 方法3:在本地类中指定搜索条件代码
  • 方法4:在匿名类中指定搜索条件代码
  • 方法5:使用Lambda表达式指定搜索条件代码
  • 方法6:使用带有Lambda表达式的标准功能接口
  • 方法7:在整个应用程序中使用Lambda表达式
  • 方法8:更广泛地使用泛型
  • 方法9:使用接受Lambda表达式作为参数的聚合操作
  • GUI应用程序中的Lambda表达式
  • Lambda表达式的语法
  • 访问封闭范围的局部变量
  • 目标打字
  • 目标类型和方法参数
  • 序列化

方法1:创建一个根据某一特性查询匹配用户的方法

最简单的方式是创建几个函数,每个函数搜索指定的用户特征,比如searchByAge()这种方法,下面的方法打印了年龄大于某特定值的所有用户:

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

这个方法是有潜在的问题的,如果引入一些变动(比如新的数据类型)这个程序会出错。假设更新了应用且变化了Person类,比如使用出生年月代替了年龄;也有可能搜索年龄的算法不同。这样你将不到不再写许多API来适应这些变化。

Lambda表达式的理想用例

假设您正在创建一个社交网络应用程序。您想要创建一个功能,使管理员可以在符合特定条件的社交网络应用程序的成员上执行任何类型的操作。下表详细描述了这种用例:

金沙澳门娱乐网址 1

假设这个社交网络应用程序的成员由以下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表达式使用高效简明的方法来完成。方法1:创建搜索匹配一个特征的成员的方法

一种简单的方法是创建几种方法; 每个方法搜索符合一个特征的成员,如性别或年龄。以下方法打印比指定年龄更早的成员:

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

注意:A 集合是一个对象,该组中的多个元素到单个单元中。集合用于存储,检索,操纵和传达聚合数据。

这种方法可能会使您的应用程序变得脆弱,这是因为引入更新(例如较新的数据类型)而导致应用程序无法正常工作的可能性。假设您升级应用程序并更改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.printPerson(); } }}

该方法通过调用该方法来检查参数中Person包含的每个实例是否满足参数中指定的搜索条件。如果方法返回一个值,那么该方法在实例上被调用。ListrosterCheckPersontestertester.testtester.testtrueprintPersonsPerson要指定搜索条件,您可以实现该 CheckPerson接口:

interface CheckPerson { boolean test;}

以下类CheckPerson通过指定方法的实现来实现接口test。该方法可以筛选符合美国Selective Service的成员:true如果Person参数为男性且年龄在18至25之间,则返回值:

class CheckPersonEligibleForSelectiveService implements CheckPerson { public boolean test { 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 { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } });

这种方法减少了所需的代码量,因为您不必为每个要执行的搜索创建一个新类。然而,匿名类的语法是庞大的,因为CheckPerson接口只包含一种方法。在这种情况下,您可以使用lambda表达式而不是匿名类,如下一节所述。方法5:使用Lambda表达式指定搜索条件代码该CheckPerson接口是一个功能接口。功能界面是只包含一个抽象方法的任何接口 。功能界面可能包含一个或多个 默认方法静态方法由于功能界面只包含一个抽象方法,因此在实现时可以省略该方法的名称。为此,您不必使用匿名类表达式,而是使用lambda表达式,该表达式在以下方法调用中突出显示:

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

有关如何定义lambda表达式的信息,请参阅Lambda表达式的语法。您可以使用标准功能界面来代替接口CheckPerson,这进一步减少了所需的代码量。

方法6:使用带有Lambda表达式的标准功能接口

Reconsider the CheckPerson interface:interface CheckPerson { boolean test;}

这是一个非常简单的界面。它是一个功能界面,因为它只包含一个抽象方法。该方法需要一个参数并返回一个 boolean值。该方法非常简单,可能不值得在应用程序中定义一个。因此,JDK定义了几个标准的功能接口,您可以在包中找到它们java.util.function。例如,您可以使用该 Predicate<T> 界面代替CheckPerson。该界面包含以下方法boolean test:

interface Predicate<T> { boolean test;}

该接口Predicate<T>是通用接口的示例。通用类型在尖括号中指定一个或多个类型参数。此接口只包含一个类型参数T。当您使用实际类型参数声明或实例化通用类型时,您将具有参数化类型。例如,参数化类型Predicate<Person>如下:

interface Predicate<Person> { boolean test;}

此参数化类型包含一个具有相同返回类型和参数的方法CheckPerson.boolean test。因此,您可以使用以下方法Predicate<T>来代替CheckPerson:

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

因此,以下方法调用与printPersons在方法3中调用时相同 :在本地类中指定搜索条件代码以获取符合选择性服务的成员:

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

该方法检查参数中Person包含的每个实例是否满足参数中指定的条件。如果实例满足由此指定的条件,则该实例将调用该方法。ListrosterPredicatetesterPersontesterprintPersronPerson

而不是调用该方法printPerson,您可以指定在Person满足指定条件的那些实例上执行的其他操作tester。您可以使用lambda表达式指定此操作。假设你想要一个类似于一个lambda表达式printPerson,一个参数(一个类型的对象Person)并返回void。记住,要使用lambda表达式,您需要实现一个功能界面。在这种情况下,您需要一个包含抽象方法的功能界面,该方法可以使用一个类型的参数Person并返回void。该 Consumer<T> 界面包含void accept具有这些特征的方法 。以下方法将p.printPerson()使用Consumer<Person>调用该方法的实例替换该调用 accept:

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

因此,以下方法调用与printPersons在方法3中调用的方法相同:在本地类中指定搜索条件代码以获取符合选择性服务的成员。用于打印成员的lambda表达式突出显示:

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

如果您想要更多地使用会员的个人资料,而不是打印出来。假设您要验证会员的个人资料或检索他们的联系信息?在这种情况下,您需要一个功能界面,其中包含一个返回值的抽象方法。该 Function<T,R> 接口包含的方法R apply。以下方法检索由参数指定的数据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 { String data = mapper.apply; block.accept; } }}

以下方法从包含在roster哪些符合选择性服务的每个成员中检索电子邮件地址,然后打印:

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

方法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 { Y data = mapper.apply; block.accept; } }}

要打印符合选择性服务的会员的电子邮件地址,请调用以下processElements方法:

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

此方法调用执行以下操作:

  • 1.从集合中获取对象的源source。在此示例中,它Person从集合中获取对象的源roster。请注意,作为roster类型集合的集合也是类型List的对象Iterable。

    1. 过滤与该Predicate对象匹配的对象tester。在此示例中,该Predicate对象是一个lambda表达式,用于指定哪些成员将具有选择性服务的资格。
  • 3.映射由指定的每个经滤波的对象的值Function的对象mapper。在此示例中,该Function对象是一个返回成员的电子邮件地址的lambda表达式。

  • 4.由指定执行每个映射对象的动作Consumer对象block。在这个例子中,该Consumer对象是一个lambda表达式,它打印一个字符串,它是Function对象返回的电子邮件地址。您可以使用聚合操作替换这些操作。

方法9:使用接受Lambda表达式作为参数的聚合操作

roster .stream() .filter( p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25) .map(p -> p.getEmailAddress .forEach(email -> System.out.println;

下表列出了方法processElements执行的每个操作与相应的聚合操作:

金沙澳门娱乐网址 2

操作filter,map并且forEach是聚合操作。从流中聚合操作流程元素,而不是直接从集合(这是在这个示例中调用的第一个方法的原因stream)。甲流是元素的序列。与集合不同,它不是存储元素的数据结构。相反,流携带来自源的值,例如通过管道的收集。甲管道是流的操作的序列,其在该示例中是filter-

  • 。map forEach另外,聚合操作通常接受lambda表达式作为参数,使您可以自定义它们的行为。

** GUI应用程序中的Lambda表达式**要在图形用户界面应用程序(如键盘操作,鼠标操作和滚动操作)中处理事件,您通常会创建事件处理程序,通常涉及实现特定接口。事件处理接口通常是功能接口; 他们往往只有一种方法。

 btn.setOnAction(new EventHandler <ActionEvent>(){ @Override public void handle(ActionEvent event){ System.out.println(“Hello World!”); } });

方法调用btn.setOnAction指定当您选择由btn对象表示的按钮时会发生什么。此方法需要一个类型的对象EventHandler<ActionEvent>。该EventHandler<ActionEvent> 界面只包含一种方法void handle。此接口是一个功能界面,因此您可以使用以下突出显示的lambda表达式来替换它:

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

方法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方法更加通用,但是每个查询都创建特定的函数都是有可以导致程序不够健壮。你可以使用接口将特定的搜索转交给需要搜索的特定类中(面向接口编程的思想——简单工厂模式)。

Lambda表达式的语法

  • 用括号括起来的逗号分隔的形式参数列表。该CheckPerson.test方法包含一个参数, p它表示Person该类的一个实例 。

注意:您可以忽略lambda表达式中参数的数据类型。另外,如果只有一个参数,可以省略括号。例如,以下lambda表达式也是有效的:

p - > p.getGender()== Person.Sex.MALE && p.getAge()> = 18 && p.getAge()<= 25
  • 箭头令牌, ->

  • 一个由单个表达式或语句块组成的主体。此示例使用以下表达式:

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

如果指定单个表达式,则Java运行时将评估表达式,然后返回其值。或者,您可以使用return语句:

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

return语句不是表达式; 在lambda表达式中,您必须用大括号括起来。但是,您不必在大括号中包含一个void方法调用。例如,以下是有效的lambda表达式:

email -> System.out.println

请注意,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; } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition =  -> a + b; IntegerMath subtraction =  -> 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表达式两个操作,addition和subtraction。该示例打印以下内容:

40 + 2 = 4220 - 10 = 10

访问封闭范围的局部变量

像本地和匿名类一样,lambda表达式可以 捕获变量 ; 它们对包围范围的局部变量具有相同的访问权限。但是,与本地和匿名类不同,lambda表达式没有任何阴影问题(有关详细信息,请参阅 阴影)。Lambda表达式是词法的范围。这意味着它们不会从超类型继承任何名称或引入新的范围界定。lambda表达式中的声明就像在封闭环境中一样被解释。以下示例 LambdaScopeTest演示如下:

import java.util.function.Consumer;public class LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel { // 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 =  -> { 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; } } public static void main(String... args) { LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel; }}

T此示例生成以下输出:

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

如果在lambda表达式的声明中替换参数x,编译器将生成一个错误:ymyConsumer

Consumer<Integer> myConsumer =  -> { // ...}

编译器生成错误“变量x已经在方法methodInFirstLevel中定义”,因为lambda表达式不会引入新的一级范围。因此,您可以直接访问封闭范围的字段,方法和局部变量。例如,lambda表达式直接访问x该方法的参数methodInFirstLevel。要访问包围类中的变量,请使用关键字this。在这个例子中,this.x指的是成员变量FirstLevel.x。

然而,像本地和匿名类一样,lambda表达式只能访问最终或有效最终的封闭块的局部变量和参数。例如,假设您在methodInFirstLevel定义语句之后立即添加以下赋值语句:

void methodInFirstLevel { x = 99; // ...}

由于这个赋值语句,变量FirstLevel.x不再是有效的最终了。因此,Java编译器会生成类似于“lambda表达式引用的本地变量必须是final”或“final”的错误消息,其中lambda表达式myConsumer尝试访问该FirstLevel.x变量:

System.out.println;

方法3:在本地类中设定特定的搜索条件

下面的方法可以打印出符合搜索条件的所有用户信息

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

这个方法通过调用tester.test方法检测每个roster列表中的元素是否满足搜索条件。如果tester.test返回true,则打印符合条件的Person实例。

通过实现CheckPerson接口实现搜索。

interface CheckPerson {
    boolean test(Person p);
}

下面的类实现了CheckPerson接口的test方法。如果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类实现了一个接口,你可以使用一个匿内部类替代本地类,通过声明一个新的内部类来满足不同的搜索。

目标打字

你如何确定一个lambda表达式的类型?回想一下选择的男性和18至25岁之间的成员的lambda表达:

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

public static void printPersons(List<Person> roster, CheckPerson tester)在方法3:在局部类指定搜索条件码

public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)在方法6:Lambda表达式使用标准的功能接口当Java运行时调用该方法时

printPersons,它期望数据类型CheckPerson,因此lambda表达式是这种类型的。但是,当Java运行时调用该方法时printPersonsWithPredicate,它期待数据类型Predicate<Person>,因此lambda表达式是这种类型的。这些方法期望的数据类型称为目标类型。要确定lambda表达式的类型,Java编译器将使用上下文的目标类型或其中找到lambda表达式的情境。因此,您只能在Java编译器可以确定目标类型的情况下使用lambda表达式:

变量声明分配回报表阵列初始化器方法或构造函数参数Lambda表达体条件表达式, ?:演员表达式目标类型和方法参数

对于方法参数,Java编译器使用其他两种语言功能来确定目标类型:重载解析和类型参数推断。

考虑以下两个功能界面( java.lang.Runnable和 java.util.concurrent.Callable<V>):

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

该方法Runnable.run不返回值,而是Callable<V>.call。假设您已经invoke按照以下方法重载了该方法

void invoke(Runnable r) { r.run();}<T> T invoke(Callable<T> c) { return c.call();}

在以下语句中将调用哪种方法?

String s = invoke - >“done”);

该方法invoke(Callable<T>)将被调用,因为该方法返回一个值; 该方法 invoke没有。在这种情况下,lambda表达式的类型() -> "done"是Callable<T>。

方法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表达式替换匿名内部类,像下面介绍的这种。

序列化

如果lambda表达式的目标类型及其捕获的参数是可序列化的,则可以 序列化它。然而,像 内部类一样,强烈地不鼓励lambda表达式的序列化。

方法5:通过Lambda表达式实搜索接口

CheckPerson接口是一个函数式接口。接口中只有一个抽象方法的接口属于函数式接口(一个函数式接口也可能包换一个活多个默认方法或者静态方法)。由于函数式接口只包含一个抽象方法,你可以在实现该方法的时候省略方法的名字。因此你可以使用lambda表达式取代匿名内部类表达式,像下面这样调用:

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

lambda表达式的语法后面会做详细介绍。你还可以使用标准的函数式接口取代CheckPerson接口,这样会进一步减少代码量。

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

CheckPerson接口是一个非常简单的接口:

interface CheckPerson {
    boolean test(Person p);
}

它只有一个抽象方法,因此它是一个函数式接口。这个函数有个一个参数和一个返回值。它太过简单以至于没有必要在你应用中定义它。因此JDK中定义了一些标准的函数式接口,可以在java.util.function包中找到。比如,你可以使用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();
        }
    }
}

那么,可以这样调用这个函数:

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

这个不是使用lamdba表达式的唯一的方式。建议使用下面的其他方式使用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();
        }
    }
}

这个方法检测roster中的每个Person实例是否满足tester的标准。如果Person实例满足tester中设定的标准,那么Person实例的信息将会被打印出来。

你可以指定一个不同的动作来执行打印满足tester中定义的搜索条件的Person实例。你可以指定这个动作是一个lambda表达式。假设你想要一个功能和printPerson一样的lambda表示式(一个参数、返回void),你需要实现一个函数式接口。在这种情况下,你需要一个包含一个只有一个Person类型参数和返回void的函数式接口。Consumer<T>接口包换一个void accept(T t)函数,它符合上述需求。下面的函数使用 Consumer<Person> 调用accept()从而取代了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);
            }
        }
}

那么可以这样调用processPersons函数:

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

如果你想对用户的信息进行更多处理而不止打印出来,那该怎么办呢?假设你想验证成员的个人信息或者获取他们的联系人的信息呢?在这种情况下,你需要一个有返回值的抽象函数的函数式接口。Function<T,R>接口包含了R apply(T t)方法,有一个参数和一个返回值。下面的方法获取参数匹配到的数据,然后根据lambda表达式代码块做相应的处理:

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

本文由js12345金沙官网登入发布于网络编程,转载请注明出处:【金沙澳门娱乐网址】深入理解Java 8 Lambda表达式

关键词: