java中lambda表达式简单用例

我对java中lambda表达式的看法是相当纠结的:

一个我这么想:lambda表达式降低了java程序的阅读体验。java程序一直不以表现力出众,正相反使Java流行的一个因素正是它的安全和保守——即使是初学者只要注意些也能写出健壮且容易维护的代码来。lambda表达式对开发人员的要求相对来说高了一层,因此也增加了一些维护难度。

另一个我这么想:作为一个码代码的,有必要学习并接受语言的新特性。如果只是因为它的阅读体验差就放弃它在表现力方面的长处,那么即使是三目表达式也有人觉得理解起来困难呢。语言也是在发展的,跟不上的就自愿被丢下好了。

我不愿意被丢下。不过非让我做出选择的话,我的决定还是比较保守的:没必要一定在java语言中使用lambda——它让目前Java圈子中的很多人不习惯,会造成人力成本的上升。如果非常喜欢的话,可以考虑使用scala。

不管怎样,我还是开始试着掌握Lambda了,毕竟工作中维护的部分代码使用了Lambda(相信我,我会逐步把它去掉的)。学习的教程是在Oracla – Java官网的相关教程。

——————————

假设目前正在创建一个社交网络应用。其中的一个特性是管理员可以对符合指定条件的会员执行某些操作,如发送消息。下面的表格详细描述了这个用例:

Field 描述
名称 要执行的操作
主要参与者 管理员
前提条件 管理员登录系统
后置条件 只对符合指定标准的会员执行操作
主成功场景 1. 管理员对要执行操作的目标会员设置过滤标准;
2. 管理员选择要执行的操作;
3. 管理员点击提交按钮;
4. 系统找到符合指定标准的会员;
5. 系统对符合指定标准的会员执行预先选择的操作。
扩展 在选择执行操作前或者点击提交按钮前,管理员可以选择是否预览符合过滤标准的会员信息。
发生频率 一天中会发生许多次。

使用下面的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表达式的强大和高效。可以在这里找到完整的代码。

方案一:一个个地创建查找符合指定标准的会员的方法

这是实现前面提到的案例最简单粗糙的方案了:就是创建几个方法、每个方法校验一项标准(比如年龄或是性别)。下面的一段代码校验了年龄大于一个指定值的情况:

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

这是一种很脆弱的方案,极有可能因为一点更新就导致应用无法运行。假如我们为Person类添加了新的成员变量或者更改了标准中衡量年龄的算法,就需要重写大量的代码来适应这种变化。再者,这里的限制也太过僵化了,比方说我们要打印年龄小于某个指定值的成员又该怎么做呢?再添加一个新方法printPersonsYoungerThan?这显然是一个笨方法。

方案二:创建更通用的方法

下面的方法较之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类,添加了诸如友好度和地理位置这样的属性又该怎么办呢。尽管这样写方法要比printPersonsYoungerThan通用性更强一些,但是如果为每一种可能的查询都写一个方法也会导致代码的脆弱。倒不如把标准检查这一块代码给独立到一个新的类中。

方案三:在一个局部类中实现标准检查

下面的方法打印了符合检索标准的会员信息:

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

在程序里使用了一个CheckPerso对象tester对List参数roster中的每个实例进行校验。如果tester.test()返回true,就会执行printPerson()方法。为了设置检索标准,需要实现CheckPerson接口。

下面的这个类实现了CheckPerson并提供了test方法的具体实现。这个类中的test方法过滤了满足在美国服兵役条件的会员信息:即性别为男、且年龄在18~25岁之间。

  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实现了一个接口,所以可以使用匿名类,而不需要再为每种标准分别定义一个内部类。

方案四:使用匿名类实现标准检查

下面调用的printPersons方法中的一个参数是匿名类。这个匿名类的作用和方案三中的CheckPersonEligibleForSelectiveService类一样:都是过滤性别为男且年龄在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表达式实现标准检查

CheckPerson接口是一个函数式接口。所谓的函数式接口就是指任何只包含一个抽象方法的接口。(一个函数式接口中也可以有多个default方法或静态方法)。既然函数式接口中只有一个抽象方法,那么在实现这个函数式接口的方法的时候可以省略掉方法的方法名。为了实现这个想法,可以使用Lambda表达式替换匿名类表达式。在下面重写的printPersons方法中,相关的代码做了高亮处理:

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

在这里还可以使用一个标准的函数接口来替换CheckPerson接口,从而进一步简化代码。

方案六:在Lambda表达式中使用标准函数式接口

再来看一下CheckPerson接口:

  interface CheckPerson {
    boolean test(Person p);
  }

这是一个非常简单的接口。因为只有一个抽象方法,所以它也是一个函数式接口。这个抽象方法只接受一个参数并返回一个boolean值。这个抽象接口太过简单了,以至于我们会考虑是否有必要在应用中定义一个这样的接口。此时可以考虑使用JDK定义的标准函数式接口,可以在java.util.function包下找到这些接口。

在这个例子中,我们就可以使用Predicate<T>接口来替换CheckPerson。在这个接口中有一个boolean test(T t)方法:

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

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

有没有注意到,这里使用Predicate<Person>作为参数类型时并没有显式指定参数类型。这里并不是唯一适用lambda表达式的地方,下面的方案会介绍更多lambda表达式的用法。

方案七:在整个应用中使用lambda表达式

再来看一下printPersonsWithPredicate 方法,考虑是否可以在这里使用lambda表达式:

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

在这个方法中使用Predicate实例tester检查了roster中的每个Person实例。如果Person实例符合tester中定义的检查标准,将会触发Person实例的printPerson方法。

除了触发printPerson方法,满足tester标准的Person实例还可以执行其他的方法。可以考虑使用lambda表达式指定要执行的方法(私以为这个特性很好,解决了java中方法不能作为对象传递的问题)。现在需要一个类似printPerson方法的lambda表达式——一个只需要一个参数且返回为void的lambda表达式。记住一点:要使用lambda表达式,需要先实现一个函数式接口。在这个例子中需要一个函数式接口,其中只包含一个抽象方法,这个抽象方法有个类型为Person的参数,且返回为void。可以看一下JDK提供的标准函数式接口Consumer<T>,它有一个抽象方法void accept(T t)正好满足这个要求。在下面的代码中使用一个Consumer<T>的实例调用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(
        roster,
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25,
        p -> p.printPerson()
    );

如果我们想做的事情不只是打印会员信息,而是更多的事情,比如验证会员身份、获取会员联系方式等等。此时,我们需要一个有返回值方法的函数式接口。JDK的标准函数式接口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)
    );

方案八:多使用泛型

再来回顾一下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)
    );

在方法的调用过程中执行了如下行为:

从一个集合中获取对象信息,在这个例子里是从集合实例roster中获取Person对象信息。
过滤能够匹配Predicate实例tester的对象。在这个例子里,Predicate对象是一个lambda表达式,它指定了过滤适龄服兵役的条件。

将过滤后的对象交给一个Function对象mapper处理,mapper会为这个对象匹配一个值。在这个例子中Function对象mapper是一个lambda表达式,它返回了每个会员的邮箱地址。

由Consumer对象block为mapper匹配的值指定一个行为。在这个例子里,Consumer对象是一个lambda表达式,它的作用是打印一个字符串,也就是Function实例mapper返回的会员邮件地址。

方案九:使用将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));

分析下如上代码的执行过程,整理如下表:


行为


聚集操作


获取对象


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())。Stream是一个数据序列。和集合不同,Stream并没有用特定的结构存储数据。相反的,Stream从一个特定的源获取数据,比如从集合获取数据,通过一个pipeline。pipeline是一个Stream操作序列,在这个例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表达式作为参数,这也给了我们许多自定义的空间。

(0)

相关推荐

  • Java编程中使用lambda表达式的奇技淫巧

    为什么使用Lambda表达式 先看几个例子: 第一个例子,在一个独立的线程中执行某项任务,我们通常这么实现: class Worker implements Runnable { public void run() { for (int i = 0; i < 100; i++) doWork(); } ... } Worker w = new Worker(); new Thread(w).start(); 第二个例子,自定义字符串比较的方法(通过字符串长度),一般这么做: class Leng

  • 深入理解Java中的Lambda表达式

    Java 8 开始出现,带来一个全新特性:使用 Lambda 表达式 (JSR-335) 进行函数式编程.今天我们要讨论的是 Lambda 的其中一部分:虚拟扩展方法,也叫做公共辩护(defender)方法.该特性可以让你在接口定义中提供方法的默认实现.例如你可以为已有的接口(如 List 和 Map)声明一个方法定义,这样其他开发者就无需重新实现这些方法,有点像抽象类,但实际却是接口.当然,Java 8 理论上还是兼容已有的库. 虚拟扩展方法为 Java 带来了多重继承的特性,尽管该团队声称与

  • java中lambda表达式语法说明

    语法说明 一个lambda表达式由如下几个部分组成: 1. 在圆括号中以逗号分隔的形参列表.在CheckPerson.test方法中包含一个参数p,代表了一个Person类的实例.注意:lambda表达式中的参数的类型是可以省略的:此外,如果只有一个参数的话连括号也是可以省略的.比如上一节曾提到的代码: p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25

  • Java函数式编程(一):你好,Lambda表达式

    第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

  • Java Lambda表达式详解和实例

    简介 Lambda表达式是Java SE 8中一个重要的新特性.lambda表达式允许你通过表达式来代替功能接口. lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块). Lambda表达式还增强了集合库. Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包. 流(stream)就如同迭代器(iterator),但附加了许多额外的功能.

  • 快速入门Java中的Lambda表达式

    Lambda简介 Lambda表达式是Java SE 8中一个重要的新特性.lambda表达式允许你通过表达式来代替功能接口. lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块). Lambda表达式还增强了集合库. Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包. 流(stream)就如同迭代器(iterator),但附加了许多

  • Java8新特性之Lambda表达式浅析

    说到java 8,首先会想到lambda(闭包)以及虚拟扩展方法(default method),这个特性早已经被各大技术网站炒得沸沸扬扬了,也是我们java 8系列开篇要讲的第一特性(JEP126 http://openjdk.java.net/jeps/126),jdk8的一些库已经应用了lambda表达式重新设计了,理解他对学习java 8新特性有着重要的意义. 一.函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是

  • Java8新特性lambda表达式有什么用(用法实例)

    我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值.现有接口迁移成为lambda风格的问题已经通过default methods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulk operation),解开lambda最强作用的神秘面纱. 1.关于JSR335 JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其

  • java中lambda表达式简单用例

    我对java中lambda表达式的看法是相当纠结的: 一个我这么想:lambda表达式降低了java程序的阅读体验.java程序一直不以表现力出众,正相反使Java流行的一个因素正是它的安全和保守--即使是初学者只要注意些也能写出健壮且容易维护的代码来.lambda表达式对开发人员的要求相对来说高了一层,因此也增加了一些维护难度. 另一个我这么想:作为一个码代码的,有必要学习并接受语言的新特性.如果只是因为它的阅读体验差就放弃它在表现力方面的长处,那么即使是三目表达式也有人觉得理解起来困难呢.语

  • Java中Lambda表达式的使用详细教程

    目录 简介 格式 实例 lambda作为参数 遍历集合 创建线程 排序 简介 说明 本文用示例展示Lambda表达式的用法.同时也会介绍Lambda的相关知识. Lambda表达式介绍 Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能. Lambda表达式是Java SE 8中一个重要的新特性. Lambda与匿名内部类 Lamda表达式指的是应用在SAM(SingleAbstractMethod,含有一个抽象

  • Java中Lambda表达式并行与组合行为

    从串行到并行 串行指一个步骤一个步骤地处理,也就是通常情况下,代码一行一行地执行. 如果将我们常用的迭代器式的循环展开的话,就是串行执行了循环体内所定义的操作: sum += arr.get(0); sum += arr.get(1); sum += arr.get(2); //... 在书的一开始,就提到Java需要支持集合的并行计算(而Lambda为这个需求提供了可能). 这些功能将全部被实现于库代码中,对于我们使用者,实现并行的复杂性被大大降低(最低程度上只需要调用相关方法). 另外,关于

  • Java中Lambda表达式基础及使用

    目录 一.举例说明 1.无参无返回 1.1 定义一个接口 1.2接口实现类 1.3 测试类 2.有参无返回代码示例 3.有参有返回 二.简单事项 1.省略模式 2.注意事项 三.Lambda表达式和匿名内部类的区别 1.所需类型不同: 2.使用限制不同: 3.实现原理不同: 标准格式: 三要素:形式参数 箭头 代码块 格式:(形式参数)->{代码块} 形式参数:如果多个参数用逗号隔开,无参留空 ->:英文中划线和大于号组成 代码块:具体要做的事 使用前提: 有一个接口 接口中有且仅有一个抽象方

  • Java中Lambda表达式之Lambda语法与作用域解析

    接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍Lambda表达式基础知识. 前言 把上一张书中的结语放到这里作为本章学习内容的开头,以此来概括Lambda表达式的优点: 提升性能.自动的并行化 更棒的API(comparing(...)细粒度的方法将成为标准) 编码风格得到改进.代码简化 反观前面几篇文章中的代码实践,以上三个优点全部得到了验证.

  • java中lambda表达式的分析与具体用法

    Lamda表达式 λ 希腊字母表中排序第十一位字母,英语名称为Lambda 避免匿名内部类定义过多 其实质属于函数式 编程的概念 (params)->expression[表达式] (params)->statement[语句] (params)->{statements} (a-> System.out.print("i like lambda–>"+a)); new Thread (()->System.out.println("多线程

  • 浅谈Java中Lambda表达式的相关操作

    为什么要使用Lambda? 可以对一个接口进行非常简洁的实现. Lambda对接口的要求? 接口中定义的抽象方法有且只有一个才可以. 传统实现一个接口需要这样做: 方法一: // 实现接口,同时必须重写接口中抽象方法 class Test implements IntrfacefN { @Override public void getUser(int a, int b) { } } // @FunctionalInterface 注解意思:函数式接口,用来做规范,有这个注解,说明此接口有且只有

  • Java中Lambda表达式的使用详解

    目录 理解函数式接口以及 Lambda表达式的发展过程 Lambda表达式及语法 一起来看看具体的使用 你需要注意什么 Lambda的实际运用 1.对集合排序 2.遍历集合 3.遍历集合(带条件) 4.代替 Runnable,开启一个线程 理解函数式接口以及 Lambda表达式的发展过程 任何接口,只包含唯一一个抽象方法,就是函数式接口 /** * lambdab表达式的发展 */ public class TestLambda1 { //3.静态内部类 static class Like2 i

  • Java中Lambda表达式用法介绍

    Lambda lambda是一个匿名函数,我们可以把lambda表达式理解为是一段可以传递的代码. lambda简明的地将代码或方法作为参数传递进去执行. "函数式编程"其核心是把函数作为值. 函数式接口 :只有一个 抽象方法的接口 称之为 函数式接口.函数式接口可以使用@FunctionalInterface进行注解. lambda表达式拆分为两部分 左侧:lambda 表达式的参数列表 右侧:lambda 表达式中所需要执行的功能,即lambda体 语法格式一:无参数,无返回值 @

  • Java中lambda表达式的基本运用

    目录 一.实现接口 二.数字排序 三.字符串排序方法 四.对象排序 总结 一.实现接口 调用一个接口中的方法,传统方法:接口类A: package lombda; /** * @author yeqv * @program A2 * @Classname A * @Date 2022/1/25 20:38 * @Email w16638771062@163.com */ public interface A { int po(int i); } 实现接口: package lombda; /**

随机推荐