澳门新浦京娱乐游戏掌握 Java 8 Lambda 表达式

澳门新浦京娱乐游戏 2

Lambda 表达式 是 Java8 中最重要的功能之一。使用 Lambda 表达式
可以替代只有一个函数的接口实现,告别匿名内部类,代码看起来更简洁易懂。Lambda
表达式 同时还提升了对 集合 框架的迭代、遍历、过滤数据的操作。

Java SE 8: Lambda Quick
Start
施工中

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

匿名内部类

在 Java 世界中,匿名内部类
可以实现在应用程序中可能只执行一次的操作。例如,在 Android
应用程序中,一个按钮的点击事件处理。你不需要为了处理一个点击事件单独编写一个独立的类,可以用匿名内部类完成该操作:

Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View view) {
        Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show();
    }

});

通过匿名内部类,虽然代码看起来不是很优雅,但是代码看起来比使用单独的类要好理解,可以直接在代码调用的地方知道点击该按钮会触发什么操作。

介绍

Lambda表达式是Java
SE8的重要新特性,提供了一个实现函数接口的简单方法。Lambda表达式改进了Collection库,使得遍历、查询和提取数据更简单。同时,新的并发机制提高了它们多核环形下的表现。

官方文档:

Functional Interfaces(函数型接口)

定义 OnClickListener 接口的代码如下:

    public interface OnClickListener {
        void onClick(View v);
    }

OnClickListener 是一个只有一个函数的接口。在 Java 8
中,这种只有一个函数的接口被称之为 “Functional Interface”。

在 Java 中 Functional Interface
用匿名内部类实现是一种非常常见的形式。除了 OnClickListener 接口以外,像
Runnable 和 Comparator 等接口也符合这种形式。

匿名内部类

匿名内部类提供了声明代码中只出现一次的类的方法。例如,在表中Swing或JavaFX引用中,需要为键盘或鼠标事件声明很多事件处理类。利用匿名内部类,可以这样写:

16  JButton testButton = new JButton("Test Button");
17  testButton.addActionListener(new ActionListener(){
18      @Override public void actionPerformed(ActionEvent ae){
19        System.out.println("Click Detected by Anon Class");
20      }
21  });

否则,每个事件都要单独声明一个类实现ActionListener接口。通过在需要的地方声明内部类,代码更易读一点。但代码仍然不够优雅,因为声明一个内部类仍需要太多代码。

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

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

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

Lambda 表达式语法

Lambda
表达式通过把匿名内部类五行代码简化为一个语句。这样使代码看起来更加简洁。

一个 Lambda 表达式 由三个组成部分:

参数列表 箭头符号 函数体

(int x, int y) -> x + y

函数体可以是单个表达式,也可以是代码块。如果是单个表达式的话,函数体直接求值并返回了。如果是代码块的话,就和普通的函数一样执行,return
语句控制调用者返回。在最外层是不能使用 break 和 continue
关键字的,在循环中可以用来跳出循环。如果代码块需要返回值的话,每个控制路径都需要返回一个值或者抛出异常。

下面是一些示例:

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

第一个表达式有两个整数型参数 x 和 y,表达式返回 x + y
的值。第二个表达式没有参数直接返回一个表达式的值 42,。 第三个有一个
string 参数,使用代码块的方式把该参数打印出来,没有返回值。

函数接口

ActionListener接口代码如下:

 1 package java.awt.event; 
2 import java.util.EventListener; 
3  
4 public interface ActionListener extends EventListener { 
5   
6   public void actionPerformed(ActionEvent e);
7  
8 }

ActionListener是一个只有一个方法的接口,在Java
SE8中,这种只有一个方法的接口成为函数接口(之前,这类接口被称为Single
Abstract Method type SAM)。
使用内部类实现函数接口在java中普遍适用。Runnable和Comparator也是相同的用法。通过使用Lambda表达式可以改进函数接口的实现。

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

Lambda 示例

Lambda表达式的语法

Lambda表达式定位于匿名内部类臃肿的代码实现,将原本5行代码压缩为一个表达式。通过水平途径解决垂直问题。
Lambda表达式由三部分组成

参数Argument List 箭头 Arrow Token 主体 Body
(int x, int y) -> x + y

主体部分可以是一个表达式或一个代码块。
表达式直接执行并返回。
代码块,代码被当做方法执行,return语句将结果返回给匿名方法的调用者。在代码块中,break
和 continue关键字非法,但在循环体中仍可以使用。

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;

Runnable Lambda

来看几个示例, 下面是一个 Runnable 的示例:

    public void runnableTest() {
        System.out.println("=== RunnableTest ===");
        // 一个匿名的 Runnable
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world one!");
            }
        };

        // Lambda Runnable
        Runnable r2 = () -> System.out.println("Hello world two!");

        // 执行两个 run 函数
        r1.run();
        r2.run();
    }

这两个实现方式都没有参数也没有返回值。Runnable lambda
表达式使用代码块的方式把五行代码简化为一个语句。

Lambda表达式示例

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); } 

第一个表达式输入两个int类型参数x、y,使用表达式方式直接返回x+y。
第二个表达式无输入,使用表达式方式返回42.
第三个表达式输入字符串,使用代码块打印字符串,没有返回值。

目标打字

你如何确定一个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>。

Comparator Lambda

在 Java 中,Comparator 接口用来排序集合。在下面的示例中一个 ArrayList
中包含了一些 Person 对象, 并依据 Person 对象的 surName 来排序。下面是
Person 类中包含的 fields:

public class Person {
    private String givenName;
    private String surName;
    private int age;
    private Gender gender;
    private String eMail;
    private String phone;
    private String address;
}

下面是分别用匿名内部类和 Lambda 表达式实现 Comparator 接口的方式:

public class ComparatorTest {
    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();

        // 使用内部类实现排序
        Collections.sort(personList, new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.getSurName().compareTo(p2.getSurName());
            }
        });

        System.out.println("=== Sorted Asc SurName ===");
        for (Person p : personList) {
            p.printName();
        }

        // 使用 Lambda 表达式实现

        // 升序排列
        System.out.println("=== Sorted Asc SurName ===");
        Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
        for (Person p : personList) {
            p.printName();
        }

        // 降序排列
        System.out.println("=== Sorted Desc SurName ===");
        Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
        for (Person p : personList) {
            p.printName();
        }
    }
}

可以看到 匿名内部类可以通过 Lambda 表达式实现。注意 第一个 Lambda
表达式定义了参数的类型为 Person;而第二个 Lambda
表达式省略了该类型定义。Lambda
表达式支持类型推倒,如果通过上下文可以推倒出所需要的类型,则可以省略类型定义。这里由于
我们把 Lambda 表达式用在一个使用泛型定义的 Comparator
地方,编译器可以推倒出这两个参数类型为 Person 。

Runnable Lambda
 6 public class RunnableTest { 
7  public static void main(String[] args) { 
8   
9    System.out.println("=== RunnableTest ===");
10  
11  // Anonymous Runnable
12  Runnable r1 = new Runnable(){
13  
14    @Override
15    public void run(){
16      System.out.println("Hello world one!");
17    }
18  };
19  
20  // Lambda Runnable
21  Runnable r2 = () -> System.out.println("Hello world two!");
22  
23  // Run em!
24  r1.run();
25  r2.run();
26  
27  }
28 }

序列化

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

Listener 表达式

最后来看看 View 点击事件的表达式写法:

view.setOnClickListener( v -> Toast.makeText(MainActivity.this, "Button Clicked", Toast.LENGTH_SHORT).show() );

注意, Lambda 表达式可以当做参数传递。类型推倒可以在如下场景使用:

  • 变量定义
  • 赋值操作
  • 返回语句
  • 数组初始化
  • 函数或者构造函数参数
  • Lambda 表达式代码块中
  • 条件表达式中 ? :
  • 强制转换表达式

Comparator Lambda

在Java中,Comparator类用来为集合排序。在下面的例子中,Person实例的队列按照surName属性排序。Person类如下

9 public class Person {
10  private String givenName;
11  private String surName;
12  private int age;
13  private Gender gender;
14  private String eMail;
15  private String phone;
16  private String address;
17 }

使用匿名内部类和Lambda表达式的例子如下:

10 public class ComparatorTest {
11 
12  public static void main(String[] args) {
13  
14    List<Person> personList = Person.createShortList();
15  
16    // Sort with Inner Class
17    Collections.sort(personList, new Comparator<Person>(){
18      public int compare(Person p1, Person p2){
19        return p1.getSurName().compareTo(p2.getSurName());
20      }
21   });
22  
23    System.out.println("=== Sorted Asc SurName ===");
24    for(Person p:personList){
25      p.printName();
26    }
27  
28    // Use Lambda instead
29  
30    // Print Asc
31    System.out.println("=== Sorted Asc SurName ===");
32    Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
33 
34    for(Person p:personList){
35      p.printName();
36    }
37  
38    // Print Desc
39    System.out.println("=== Sorted Desc SurName ===");
40    Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
41 
42    for(Person p:personList){
43      p.printName();
44    }
45  
46  }
47 }

注意到第一个Lambda表达式声明了参数类型,第二个没有声明。Lambda表达式支持
target typing(泛型目标类型推断),通过上下文推断对象的类型。

使用 Lambda 表达式提升代码

本节通过一个示例来看看 Lambda 表达式 如何提升你的代码。Lambda
表达式可以更好的支持不要重复自己(DRY)原则并且让代码看起来更加简洁易懂。

Listener Lambda

13 public class ListenerTest {
14  public static void main(String[] args) {
15  
16    JButton testButton = new JButton("Test Button");
17    testButton.addActionListener(new ActionListener(){
18      @Override public void actionPerformed(ActionEvent ae){
19        System.out.println("Click Detected by Anon Class");
20      }
21    });
22  
23    testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listner"));
24  
25    // Swing stuff
26    JFrame frame = new JFrame("Listener Test");
27    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
28    frame.add(testButton, BorderLayout.CENTER);
29    frame.pack();
30    frame.setVisible(true);
31  
32  }
33 }

一个常见的查询案例

编码生涯中一个很常见的案例就是从一个集合中查找出符合要求的数据。例如有很多人,每个人都带有很多属性,需要从这里找出符合一些条件的人。

在本示例中,我们需要查找符合三个条件的人群:

– 司机:年龄在 16 以上的人才能成为司机

– 需要服役的人:年龄在 18到25岁的男人

– 飞行员:年龄在 23 到 65 岁的人

找到这些人后,我们可以给这些人发邮件、打电话
告诉他们可以来考驾照、需要服役了等。

利用Lambda表达式改进代码

Lambda表达式支持了 Don`t repeat yourselt
DRY原则,使代码更简洁,更易读。

Person Class

Person 类代表每个人,该类具有如下属性:

package com.example.lambda;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * @author MikeW
 */
public class Person {
  private String givenName;
  private String surName;
  private int age;
  private Gender gender;
  private String eMail;
  private String phone;
  private String address;

  public static class Builder{

    private String givenName="";
    private String surName="";
    private int age = 0;
    private Gender gender = Gender.FEMALE;
    private String eMail = "";
    private String phone = "";
    private String address = "";

    public Builder givenName(String givenName){
      this.givenName = givenName;
      return this;
    }

    public Builder surName(String surName){
      this.surName = surName;
      return this;
    }

    public Builder age (int val){
      age = val;
      return this;
    }

    public Builder gender(Gender val){
      gender = val;
      return this;
    }

    public Builder email(String val){
      eMail = val;
      return this;
    }

    public Builder phoneNumber(String val){
      phone = val;
      return this;
    }

    public Builder address(String val){
      address = val;
      return this;
    }

    public Person build(){
      return new Person(this);
    }
  }

  private Person(){
    super();
  }

  private Person(Builder builder){
    givenName = builder.givenName;
    surName = builder.surName;
    age = builder.age;
    gender = builder.gender;
    eMail = builder.eMail;
    phone = builder.phone;
    address = builder.address;

  }

  public String getGivenName(){
    return givenName;
  }

  public String getSurName(){
    return surName;
  }

  public int getAge(){
    return age;
  }

  public Gender getGender(){
    return gender;
  }

  public String getEmail(){
    return eMail;
  }

  public String getPhone(){
    return phone;
  }

  public String getAddress(){
    return address;
  }

  public void print(){
    System.out.println(
      "nName: " + givenName + " " + surName + "n" + 
      "Age: " + age + "n" +
      "Gender: " + gender + "n" + 
      "eMail: " + eMail + "n" + 
      "Phone: " + phone + "n" +
      "Address: " + address + "n"
                );
  } 

  @Override
  public String toString(){
    return "Name: " + givenName + " " + surName + "n" + "Age: " + age + "  Gender: " + gender + "n" + "eMail: " + eMail + "n";
  } 

  public static List<Person> createShortList(){
    List<Person> people = new ArrayList<>();

    people.add(
      new Builder()
            .givenName("Bob")
            .surName("Baker")
            .age(21)
            .gender(Gender.MALE)
            .email("bob.baker@example.com")
            .phoneNumber("201-121-4678")
            .address("44 4th St, Smallville, KS 12333")
            .build() 
      );

    people.add(
      new Builder()
            .givenName("Jane")
            .surName("Doe")
            .age(25)
            .gender(Gender.FEMALE)
            .email("jane.doe@example.com")
            .phoneNumber("202-123-4678")
            .address("33 3rd St, Smallville, KS 12333")
            .build() 
      );

    people.add(
      new Builder()
            .givenName("John")
            .surName("Doe")
            .age(25)
            .gender(Gender.MALE)
            .email("john.doe@example.com")
            .phoneNumber("202-123-4678")
            .address("33 3rd St, Smallville, KS 12333")
            .build()
    );

    people.add(
      new Builder()
            .givenName("James")
            .surName("Johnson")
            .age(45)
            .gender(Gender.MALE)
            .email("james.johnson@example.com")
            .phoneNumber("333-456-1233")
            .address("201 2nd St, New York, NY 12111")
            .build()
    );

    people.add(
      new Builder()
            .givenName("Joe")
            .surName("Bailey")
            .age(67)
            .gender(Gender.MALE)
            .email("joebob.bailey@example.com")
            .phoneNumber("112-111-1111")
            .address("111 1st St, Town, CA 11111")
            .build()
    );

    people.add(
      new Builder()
            .givenName("Phil")
            .surName("Smith")
            .age(55)
            .gender(Gender.MALE)
            .email("phil.smith@examp;e.com")
            .phoneNumber("222-33-1234")
            .address("22 2nd St, New Park, CO 222333")
            .build()
    );

    people.add(
      new Builder()
            .givenName("Betty")
            .surName("Jones")
            .age(85)
            .gender(Gender.FEMALE)
            .email("betty.jones@example.com")
            .phoneNumber("211-33-1234")
            .address("22 4th St, New Park, CO 222333")
            .build()
    );

    return people;
  }

}

Person 类使用一个 Builder 来创建新的对象。 通过 createShortList
函数来创建一些模拟数据。

普通的查询场景

代码中常见的场景是遍历数据集合查找符合条件的数据。给定一群人,不同的查询条件,查询出符合条件的人。
本例中,我们需要找出三类人群:

  • 司机:年龄大于16岁
  • 适龄兵役者:年龄18到25岁
  • 飞行员:年龄23到65岁
    查询结果直接打印在控制台,信息包括姓名、年龄和某个特定信息(电邮地址、电话号码)。
    Person类

10 public class Person {
11  private String givenName;
12  private String surName;
13  private int age;
14  private Gender gender;
15  private String eMail;
16  private String phone;
17  private String address;
18 } 

第一轮

RoboContactsMethods.java
1 package com.example.lambda; 
2  
3 import java.util.List; 
4  
5 /** 
6  * 
7  * 
@author MikeW 
8  */ 
9 public class RoboContactMethods { 
10   
11  public void callDrivers(List<Person> pl){ 
12    for(Person p:pl){ 
13      if (p.getAge() >= 16){ 
14        roboCall(p); 
15      } 
16    } 
17  } 
18   
19  public void emailDraftees(List<Person> pl){ 
20    for(Person p:pl){ 
21      if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
22        roboEmail(p); 
23      } 
24    } 
25  } 
26   
27  public void mailPilots(List<Person> pl){ 
28    for(Person p:pl){ 
29      if (p.getAge() >= 23 && p.getAge() <= 65){ 
30        roboMail(p); 
31      } 
32    } 
33  } 
34   
35   
36  public void roboCall(Person p){ 
37    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); 
38  } 
39   
40  public void roboEmail(Person p){ 
41    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail()); 
42  } 
43   
44  public void roboMail(Person p){ 
45    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress()); 
46  } 
47  
48 }

这一实现的缺点:

  • 没有遵守DRY原则
  • 重复使用循环机制
  • 每个查询条件对应一个方法
  • 代码无法扩展,如果查询条件发生变化,需要修改代码。

常见的实现方式

有 Person 类和搜索的条件了,现在可以撰写一个 RoboContact
类来搜索符合条件的人了:

public class RoboContactMethods {

  public void callDrivers(List<Person> pl){
    for(Person p:pl){
      if (p.getAge() >= 16){
        roboCall(p);
      }
    }
  }

  public void emailDraftees(List<Person> pl){
    for(Person p:pl){
      if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
        roboEmail(p);
      }
    }
  }

  public void mailPilots(List<Person> pl){
    for(Person p:pl){
      if (p.getAge() >= 23 && p.getAge() <= 65){
        roboMail(p);
      }
    }
  }

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

这里分别定义了 callDrivers、 emailDraftees 和 mailPilots
三个函数,每个函数的名字都表明了他们实现的功能。在每个函数中都包含了搜索的条件,但是这个实现由一些问题:

  • 没有遵守 DRY 原则
  • 每个函数都重复了一个循环操作
  • 每个函数都需要重新写一次查询条件
  • 每个搜索场景都需要很多代码来实现
  • 代码没有灵活性。如果搜索条件改变了,需要修改代码的多个地方来符合新的需求。并且代码也不好维护。

重构查询方法

通过匿名内部类实现。声明MyTest接口,只有一个条件验证函数,返回boolean值。查询条件在方法调用时传递。接口定义如下:

6 public interface MyTest<T> {
7  public boolean test(T t);
8 }

更新后的实现如下:

RoboContactsAnon.java
 9 public class RoboContactAnon {
10 
11  public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
12    for(Person p:pl){
13      if (aTest.test(p)){
14        roboCall(p);
15      }
16    }
17  }
18 
19  public void emailContacts(List<Person> pl, MyTest<Person> aTest){
20    for(Person p:pl){
21      if (aTest.test(p)){
22        roboEmail(p);
23      }
24    }
25  }
26 
27  public void mailContacts(List<Person> pl, MyTest<Person> aTest){
28    for(Person p:pl){
29       if (aTest.test(p)){
30         roboMail(p);
31      }
32    }
33  } 
34  
35  public void roboCall(Person p){
36    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
37  }
38  
39  public void roboEmail(Person p){
40    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
41  }
42  
43  public void roboMail(Person p){
44    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
45  }
46  
47 }

代码仍然臃肿,可读性不高,每个查询条件都需要单独实现。

重构这些函数

如何改进这些问题呢?如果把搜索条件判断提取出来,放到单独的地方是个不错的想法。

public class RoboContactMethods2 {

  public void callDrivers(List<Person> pl){
    for(Person p:pl){
      if (isDriver(p)){
        roboCall(p);
      }
    }
  }

  public void emailDraftees(List<Person> pl){
    for(Person p:pl){
      if (isDraftee(p)){
        roboEmail(p);
      }
    }
  }

  public void mailPilots(List<Person> pl){
    for(Person p:pl){
      if (isPilot(p)){
        roboMail(p);
      }
    }
  }

  public boolean isDriver(Person p){
    return p.getAge() >= 16;
  }

  public boolean isDraftee(Person p){
    return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
  }

  public boolean isPilot(Person p){
    return p.getAge() >= 23 && p.getAge() <= 65;
  }

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

搜索条件判断封装到一个函数中了,比第一步的实现有点改进。搜索测试条件可以重用,但是这里还是有一些重复的代码并且每个搜索用例还是需要一个额外的函数。是否有更好的方法把搜索条件传递给函数?

Lambda表达式

java.util.function
在Java
SE8中提供了JUF包有多个标准函数接口,在本例中,Predicate接口满足我们的需要。

3 public interface Predicate<T> {
4  public boolean test(T t);
5 }

本例最终形态:

RoboContactsLambda.java
1 package com.example.lambda; 
2  
3 import java.util.List; 
4 import java.util.function.Predicate; 
5  
6 /**
7  * 
8  * @author MikeW 
9  */ 
10 public class RoboContactLambda { 
11  public void phoneContacts(List<Person> pl, Predicate<Person> pred){ 
12    for(Person p:pl){ 
13      if (pred.test(p)){ 
14        roboCall(p); 
15      } 
16    } 
17  } 
18  
19  public void emailContacts(List<Person> pl, Predicate<Person> pred){ 
20    for(Person p:pl){ 
21      if (pred.test(p)){ 
22        roboEmail(p); 
23      } 
24    } 
25  } 
26  
27  public void mailContacts(List<Person> pl, Predicate<Person> pred){ 
28    for(Person p:pl){ 
29      if (pred.test(p)){ 
30        roboMail(p); 
31      } 
32    } 
33  } 
34   
35  public void roboCall(Person p){ 
36    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); 
37  } 
38   
39  public void roboEmail(Person p){ 
40    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail()); 
41  } 
42   
43  public void roboMail(Person p){ 
44    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress()); 
45  } 
46  
47 }

RoboCallTest04.java
1 package com.example.lambda; 
2  
3 import java.util.List; 
4 import java.util.function.Predicate; 
5  
6 /** 
7  * 
8  * @author MikeW 
9  */ 
10 public class RoboCallTest04 { 
11   
12  public static void main(String[] args){  
13  
14    List<Person> pl = Person.createShortList(); 
15    RoboContactLambda robo = new RoboContactLambda(); 
16   
17    // Predicates 
18    Predicate<Person> allDrivers = p -> p.getAge() >= 16; 
19    Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE; 
20    Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65; 
21   
22    System.out.println("n==== Test 04 ===="); 
23    System.out.println("n=== Calling all Drivers ==="); 
24    robo.phoneContacts(pl, allDrivers); 
25   
26    System.out.println("n=== Emailing all Draftees ==="); 
27    robo.emailContacts(pl, allDraftees); 
28   
29    System.out.println("n=== Mail all Pilots ==="); 
30    robo.mailContacts(pl, allPilots); 
31   
32    // Mix and match becomes easy 
33    System.out.println("n=== Mail all Draftees ==="); 
34    robo.mailContacts(pl, allDraftees);  
35   
36    System.out.println("n=== Call all Pilots ==="); 
37    robo.phoneContacts(pl, allPilots);  
38   
39    } 
40 }

代码紧凑易读,同时没有重复代码问题。

JUF包

  • Predicate: 传入对象,返回boolean值
  • Consumer: 传入对象,没有返回值
  • Function: 传入类型T对象,返回类型U对象
  • Supplier: 无传入值,返回T类型对象
  • UnaryOperator: 一元操作,传入T类型,返回T类型
  • BinaryOperator: 二元操作,传入T类型,返回T类型

匿名类

在 lambda 表达式出现之前,匿名内部类是一种选择。例如,我们可以定义个
MyTest 接口,里面有个 test 函数,该函数有个参数 t 然后返回一个 boolean
值告诉该 t 是否符合条件。该接口定义如下:

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

使用该接口的实现搜索功能的改进代码如下:

public class RoboContactAnon {

  public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
    for(Person p:pl){
      if (aTest.test(p)){
        roboCall(p);
      }
    }
  }

  public void emailContacts(List<Person> pl, MyTest<Person> aTest){
    for(Person p:pl){
      if (aTest.test(p)){
        roboEmail(p);
      }
    }
  }

  public void mailContacts(List<Person> pl, MyTest<Person> aTest){
    for(Person p:pl){
      if (aTest.test(p)){
        roboMail(p);
      }
    }
  }  

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

这比之前的代码又改进了一步,现在只需要执行
3个函数就可以实现搜索功能了。但是调用这些代码需要使用匿名内部类,这样调用的代码看起来非常丑:

public class RoboCallTest03 {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();
    RoboContactAnon robo = new RoboContactAnon();

    System.out.println("n==== Test 03 ====");
    System.out.println("n=== Calling all Drivers ===");
    robo.phoneContacts(pl, 
        new MyTest<Person>(){
          @Override
          public boolean test(Person p){
            return p.getAge() >=16;
          }
        }
    );

    System.out.println("n=== Emailing all Draftees ===");
    robo.emailContacts(pl, 
        new MyTest<Person>(){
          @Override
          public boolean test(Person p){
            return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
          }
        }
    );

    System.out.println("n=== Mail all Pilots ===");
    robo.mailContacts(pl, 
        new MyTest<Person>(){
          @Override
          public boolean test(Person p){
            return p.getAge() >= 23 && p.getAge() <= 65;
          }
        }
    );

  }
}

这就是大家深恶痛绝的匿名内部类嵌套问题,五行代码中只有一行是真正有用的代码,但是其他四行模板代码每次都要重新来一遍。

Lambda表达式与Collections

Lambda 表达式派上用场了

Lambda 表达式可以完美的解决该问题。前面我们已经看到了 Lambda
表达式如何解决 OnClickListener 问题的了。

在看看这里 Lambda 表达式如何实现的之前,我们先来看看 Java 8
中的一个新包:/java

在上一个示例中,MyTest functional interface
作为函数的参数。但是如果每次都需要我们自己自定义一个这样的接口是不是比较繁琐呢?
所以 Java 8 提供了这个 java.util.function 包,里面定义了几十个常用的
functional interface。这里 Predicate 这个接口符合我们的要求:

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

test
函数需要一个泛型的参数然后返回一个布尔值。过滤一个对象就需要这样的操作。下面是如何用
Lambda 表达式实现搜索的代码:

public class RoboContactLambda {
  public void phoneContacts(List<Person> pl, Predicate<Person> pred){
    for(Person p:pl){
      if (pred.test(p)){
        roboCall(p);
      }
    }
  }

  public void emailContacts(List<Person> pl, Predicate<Person> pred){
    for(Person p:pl){
      if (pred.test(p)){
        roboEmail(p);
      }
    }
  }

  public void mailContacts(List<Person> pl, Predicate<Person> pred){
    for(Person p:pl){
      if (pred.test(p)){
        roboMail(p);
      }
    }
  }  

  public void roboCall(Person p){
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
  }

  public void roboEmail(Person p){
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
  }

  public void roboMail(Person p){
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
  }

}

这样使用 Lambda 表达式就解决了这个匿名内部类的问题,下面是使用 Lambda
表达式来调用这些搜索函数的代码:

public class RoboCallTest04 {

  public static void main(String[] args){ 

    List<Person> pl = Person.createShortList();
    RoboContactLambda robo = new RoboContactLambda();

    // Predicates
    Predicate<Person> allDrivers = p -> p.getAge() >= 16;
    Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
    Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

    System.out.println("n==== Test 04 ====");
    System.out.println("n=== Calling all Drivers ===");
    robo.phoneContacts(pl, allDrivers);

    System.out.println("n=== Emailing all Draftees ===");
    robo.emailContacts(pl, allDraftees);

    System.out.println("n=== Mail all Pilots ===");
    robo.mailContacts(pl, allPilots);

    // Mix and match becomes easy
    System.out.println("n=== Mail all Draftees ===");
    robo.mailContacts(pl, allDraftees);  

    System.out.println("n=== Call all Pilots ===");
    robo.phoneContacts(pl, allPilots);    

  }
}

上面的示例代码可以在这里下载:RoboCallExample.zip

循环

首先是所有collection类支持的forEach方法。下面的例子展示打印Person队列的各种方法。

Test01ForEach.java
11 public class Test01ForEach {
12  
13  public static void main(String[] args) {
14  
15    List<Person> pl = Person.createShortList();
16  
17    System.out.println("n=== Western Phone List ===");
18    pl.forEach( p -> p.printWesternName() );
19  
20    System.out.println("n=== Eastern Phone List ===");
21    pl.forEach(Person::printEasternName);
22  
23    System.out.println("n=== Custom Phone List ===");
24    pl.forEach(p -> { System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail())); });
25  
26  }
27 
28 }

18行使用Lambda表达式打印名字,21行使用方法引用调用静态方法,24行注意Lambda表达式嵌套式的参数名。

java.util.function 包

该包包含了很多常用的接口,比如:

– Predicate: 判断是否符合某个条件

– Consumer: 使用参数对象来执行一些操作

– Function: 把对象 T 变成 U

– Supplier:提供一个对象 T (和工厂方法类似)

– UnaryOperator: A unary operator from T -> T

– BinaryOperator: A binary operator from (T, T) -> T

可以详细看看这个包里面都有哪些接口,然后思考下如何用 Lambda
表达式来使用这些接口。

链式过滤

filter方法接收Predicate实例,过滤集合,返回过滤后的结果。

Test02Filter.java
 9 public class Test02Filter {
10  
11  public static void main(String[] args) {
12 
13    List<Person> pl = Person.createShortList();
14  
15    SearchCriteria search = SearchCriteria.getInstance();
16  
17    System.out.println("n=== Western Pilot Phone List ===");
18 
19    pl.stream().filter(search.getCriteria("allPilots"))
20      .forEach(Person::printWesternName);
21  
22  
23    System.out.println("n=== Eastern Draftee Phone List ===");
24 
25    pl.stream().filter(search.getCriteria("allDraftees"))
26      .forEach(Person::printEasternName);
27  
28  }
29 }

改进人名的输出方式

比如在上面的示例中
,把找到的人名字给打印出来,但是不同的地方打印的格式要求不一样,比如有些地方要求把
姓 放到 名字的前面打印出来;而有些地方要求把 名字 放到 姓
的前面打印出来。 下面来看看如何实现这个功能:

懒加载

这里的lazy和eager没有想到合适的翻译,保留原文

常见的实现

两种不同打印人名的实现方式:

  public void printWesternName(){

    System.out.println("nName: " + this.getGivenName() + " " + this.getSurName() + "n" +
             "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "n" +
             "EMail: " + this.getEmail() + "n" + 
             "Phone: " + this.getPhone() + "n" +
             "Address: " + this.getAddress());
  }

  public void printEasternName(){

    System.out.println("nName: " + this.getSurName() + " " + this.getGivenName() + "n" +
             "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "n" +
             "EMail: " + this.getEmail() + "n" + 
             "Phone: " + this.getPhone() + "n" +
             "Address: " + this.getAddress());
  }

Function 接口非常适合这类情况,该接口的 apply 函数是这样定义的:

public R apply(T t){ }

参数为泛型类型 T 返回值为泛型类型 R。例如把 Person 类当做参数而 String
当做返回值。这样可以用该函数实现一个更加灵活的打印人名的实现:

  public String printCustom(Function <Person, String> f){
      return f.apply(this);
  }

很简单,一个 Function 对象作为参数,返回一个 字符串。

下面是测试打印的程序:

public class NameTestNew {

  public static void main(String[] args) {

    System.out.println("n==== NameTestNew ===");

    List<Person> list1 = Person.createShortList();

    // Print Custom First Name and e-mail
    System.out.println("===Custom List===");
    for (Person person:list1){
        System.out.println(
            person.printCustom(p -> "Name: " + p.getGivenName() + " EMail: " + p.getEmail())
        );
    }

    // Define Western and Eastern Lambdas

    Function<Person, String> westernStyle = p -> {
      return "nName: " + p.getGivenName() + " " + p.getSurName() + "n" +
             "Age: " + p.getAge() + "  " + "Gender: " + p.getGender() + "n" +
             "EMail: " + p.getEmail() + "n" + 
             "Phone: " + p.getPhone() + "n" +
             "Address: " + p.getAddress();
    };

    Function<Person, String> easternStyle =  p -> "nName: " + p.getSurName() + " " 
            + p.getGivenName() + "n" + "Age: " + p.getAge() + "  " + 
            "Gender: " + p.getGender() + "n" +
            "EMail: " + p.getEmail() + "n" + 
            "Phone: " + p.getPhone() + "n" +
            "Address: " + p.getAddress();   

    // Print Western List
    System.out.println("n===Western List===");
    for (Person person:list1){
        System.out.println(
            person.printCustom(westernStyle)
        );
    }

    // Print Eastern List
    System.out.println("n===Eastern List===");
    for (Person person:list1){
        System.out.println(
            person.printCustom(easternStyle)
        );
    }

  }
}

上面的示例中演示了各种使用方式。也可以把 Lambda
表达式保存到一个变量中,然后用这个变量来调用函数。

以上代码可以在这里下载:LambdaFunctionExamples.zip

Getting Lazy

通过向Collection包种加入新的枚举方式,java开发人员可以做更多的代码优化。
Laziness:指系统仅在必要时处理必须处理的对象。在上面的例子中,forEach是lazy模式的,因为这次遍历仅仅涉及两个Person对象,后续操作只发生在过滤后的对象上,代码的效率提高了。
Eagerness:代码遍历整个对象队列执行操作。

通过将forEach加入collection包,代码可以在合适的地方进行Lazy优化,在其他需要eager模式(如求和或求平均值)的地方仍使用eager模式。这一方式使得代码更高效,更有弹性。

当集合遇到 Lambda 表达式

前面介绍了如何配合 Function 接口来使用 Lambda 表达式。其实 Lambda
表达式最强大的地方是配合集合使用。

在前面的示例中我们多次用到了集合。并且一些使用 Lambda 表达式
的地方也改变了我们使用集合的方式。这里我们再来介绍一些配合集合使用的高级用法。

我们可以把前面三种搜索条件封装到一个 SearchCriteria 类中:

public class SearchCriteria {

  private final Map<String, Predicate<Person>> searchMap = new HashMap<>();

  private SearchCriteria() {
    super();
    initSearchMap();
  }

  private void initSearchMap() {
    Predicate<Person> allDrivers = p -> p.getAge() >= 16;
    Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
    Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

    searchMap.put("allDrivers", allDrivers);
    searchMap.put("allDraftees", allDraftees);
    searchMap.put("allPilots", allPilots);

  }

  public Predicate<Person> getCriteria(String PredicateName) {
    Predicate<Person> target;

    target = searchMap.get(PredicateName);

    if (target == null) {

      System.out.println("Search Criteria not found... ");
      System.exit(1);

    }

    return target;

  }

  public static SearchCriteria getInstance() {
    return new SearchCriteria();
  }
}

每个 Predicate 示例都保存在这个类中,然后可以在后面测试代码中使用。

流方法

stream方法以Collection对象为输入,以StreamInterface对象作为输出。Stream就像Iterator,只能遍历一次,不能修改其中的对象。Stream支持单线程和并行执行。

循环

先来看看结合中的 forEach 函数如何配合 Lambda 表达式使用:

public class Test01ForEach {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    System.out.println("n=== Western Phone List ===");
    pl.forEach( p -> p.printWesternName() );

    System.out.println("n=== Eastern Phone List ===");
    pl.forEach(Person::printEasternName);

    System.out.println("n=== Custom Phone List ===");
    pl.forEach(p -> { System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail())); });

  }

}

第一个使用了标准的 Lambda 表达式,调用 Person 对象的 printWesternName
函数来打印名字。而第二个用户则演示了如何使用函数引用(method
reference)
。如果要执行对象上的一个函数则这种函数引用的方式可以替代标准的
Lambda 的语法。最后一个演示了如何 printCustom 函数。注意查看在 Lambda
表达式里面嵌套 Lambda 表达式的时候,参数的名字是有变化的。(第一个
Lambda 表达式的参数为 p 而第二个为 r)

获取结果集

Stream操作的结果可以通过创建新的collection对象保存。下面的例子展示了如何将集合遍历的结果存入新的集合对象中。

Test03toList.java
10 public class Test03toList {
11  
12  public static void main(String[] args) {
13  
14    List<Person> pl = Person.createShortList();
15  
16    SearchCriteria search = SearchCriteria.getInstance();
17  
18    // Make a new list after filtering.
19    List<Person> pilotList = pl
20      .stream()
21      .filter(search.getCriteria("allPilots"))
22      .collect(Collectors.toList());
23  
24    System.out.println("n=== Western Pilot Phone List ===");
25    pilotList.forEach(Person::printWesternName);
26 
27  }
28 
29 }

Chaining and Filters

除了循环迭代集合以外,还可以串联多个函数的调用。如下所示:

public class Test02Filter {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    SearchCriteria search = SearchCriteria.getInstance();

    System.out.println("n=== Western Pilot Phone List ===");

    pl.stream().filter(search.getCriteria("allPilots"))
      .forEach(Person::printWesternName);

    System.out.println("n=== Eastern Draftee Phone List ===");

    pl.stream().filter(search.getCriteria("allDraftees"))
      .forEach(Person::printEasternName);

  }
}

先把集合转换为 stream
流,然后就可以串联调用多个操作了。这里先用搜索条件过滤集合,然后在符合过滤条件的新集合上执行循环打印操作。

集合计算

下面的例子展示了如何利用map方法获取对象的某个值,然后执行计算操作。注意Stream是并行执行的,返回值也略有不同。

Test04Map.java
10 public class Test04Map {
11 
12  public static void main(String[] args) {
13    List<Person> pl = Person.createShortList();
14  
15    SearchCriteria search = SearchCriteria.getInstance();
16  
17    // Calc average age of pilots old style
18    System.out.println("== Calc Old Style ==");
19    int sum = 0;
20    int count = 0;
21  
22    for (Person p:pl){
23      if (p.getAge() >= 23 && p.getAge() <= 65 ){
24        sum = sum + p.getAge();
25        count++;
26      }
27    }
28  
29    long average = sum / count;
30    System.out.println("Total Ages: " + sum);
31    System.out.println("Average Age: " + average);
32  
33  
34    // Get sum of ages
35    System.out.println("n== Calc New Style ==");
36    long totalAge = pl
37      .stream()
38      .filter(search.getCriteria("allPilots"))
39      .mapToInt(p -> p.getAge())
40      .sum();
41 
42    // Get average of ages
43    OptionalDouble averageAge = pl
44      .parallelStream()
45      .filter(search.getCriteria("allPilots"))
46      .mapToDouble(p -> p.getAge())
47      .average();
48 
49    System.out.println("Total Ages: " + totalAge);
50    System.out.println("Average Age: " + averageAge.getAsDouble()); 
51  
52    }
53  
54 }

Getting Lazy

上面演示的功能有用,但是集合中已经有循环方法了为啥还需要添加一个新的循环的方式呢?
通过把循环迭代集合的功能实现到类库中,Java
开发者可以做更多的代码优化。要进一步解释这个概念,需要先了解一些术语:

  • Laziness:在编程语言中,Laziness
    代表只有当你需要处理该对象的时候才去处理他们。在上面的示例中,最后一种循环变量的方式为
    lazy 的,因为通过搜索条件的对象只有 2
    个留着集合中,最终的打印人名只会发生在这两个对象上。
  • Eagerness: 在集合中的每个对象上都执行操作别称之为 eager。例如一个
    增强的 for 循环遍历一个集合去处理里面的两个对象,并称之为更加 eager

总结

  • 匿名内部类
  • Lambda表达式替代匿名内部类
  • Lambda表达式的语法
  • Function包的Predicate借口实现集合过滤操作
  • Collections包中增加的Lambda表达式特性

stream 函数

前面的示例中,在过滤和循环操作之前,先调用了stream
函数。该函数把集合对象变为一个 java.util.stream.Stream 对象。在 Stream
对象上可以串联调用各种操作。默认情况下,一个对象被处理后在 stream
中就不可用了。所以一个特定 stream 对象上的串联操作只能执行一次。 同时
Stream
还可以是顺序(默认如此)执行还可以并行执行。最后我们会演示并行执行的示例。

变化和结果(Mutation and Results)

前面已经说了, Stream 使用后就不能再次使用了。因此,在 Stream
中的对象状态不能改变,也就是要求每个元素都是不可变的。但是,如果你想在串联操作中返回对象该如何办呢?
可以把结果保存到一个新的集合中。如下所示:

public class Test03toList {

  public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    SearchCriteria search = SearchCriteria.getInstance();

    // Make a new list after filtering.
    List<Person> pilotList = pl
            .stream()
            .filter(search.getCriteria("allPilots"))
            .collect(Collectors.toList());

    System.out.println("n=== Western Pilot Phone List ===");
    pilotList.forEach(Person::printWesternName);

  }

}

上面示例中的 collect
函数把过滤的结果保存到一个新的结合中。然后我们可以遍历这个集合。

使用 map 来计算结果

map 函数通常配合 filter 使用。该
函数使用一个对象并把他转换为另外一个对象。下面显示了如何通过map
来计算所有人的年龄之和。

public class Test04Map {

  public static void main(String[] args) {
    List<Person> pl = Person.createShortList();

    SearchCriteria search = SearchCriteria.getInstance();

    // Calc average age of pilots old style
    System.out.println("== Calc Old Style ==");
    int sum = 0;
    int count = 0;

    for (Person p:pl){
      if (p.getAge() >= 23 && p.getAge() <= 65 ){
        sum = sum + p.getAge();
        count++;
      }
    }

    long average = sum / count;
    System.out.println("Total Ages: " + sum);
    System.out.println("Average Age: " + average);

    // Get sum of ages
    System.out.println("n== Calc New Style ==");
    long totalAge = pl
            .stream()
            .filter(search.getCriteria("allPilots"))
            .mapToInt(p -> p.getAge())
            .sum();

    // Get average of ages
    OptionalDouble averageAge = pl
            .parallelStream()
            .filter(search.getCriteria("allPilots"))
            .mapToDouble(p -> p.getAge())
            .average();

    System.out.println("Total Ages: " + totalAge);
    System.out.println("Average Age: " + averageAge.getAsDouble());    

  }

}

第一种使用传统的 for 循环来计算平均年龄。第二种使用 map 把 person
对象转换为其年龄的整数值,然后计算其总年龄和平均年龄。

在计算平均年龄时候还调用了 parallelStream
函数,所以平均年龄可以并行的计算。

上面的示例代码可以在这里下载:
LambdaCollectionExamples.zip

总结

感觉如何?是不是觉得 Lambda 表达式棒棒哒,亟不可待的想在项目中使用了吧。
神马? 你说 Andorid 不支持 Java 8 不能用 Lambda
表达式。好吧,其实你可以使用
gradle-retrolambda 插件把
Lambda 表达式 抓换为 Java 7 版本的代码。 还不赶紧去试试!!

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图