0

0

java中lambda表达式简单用例

高洛峰

高洛峰

发布时间:2017-01-23 15:08:30

|

1682人浏览过

|

来源于php中文网

原创

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

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

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

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

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

立即学习Java免费学习笔记(深入)”;

——————————

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

java中lambda表达式简单用例

使用下面的Person类来表示社交网络中的会员信息:

public class Person {
  
  public enum Sex {
    MALE, FEMALE
  }
  
  String name;
  LocalDate birthday;
  Sex gender;
  String emailAddress;
  
  public int getAge() {
    // ...
  }
  
  public void printPerson() {
    // ...
  }
}

Favird No-Code Tools
Favird No-Code Tools

无代码工具的聚合器

下载

假设所有的会员都保存在一个List实例中。

这一节我们从一个非常简单的方法开始,然后尝试使用局部类和匿名类进行实现,到最后会逐步深入体验Lambda表达式的强大和高效。可以在这里找到完整的代码。

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

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

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

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

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

下面的方法较之printPersonsOlderThan适应性更好一些;这个方法打印了在指定年龄段内的会员信息:

public static void printPersonsWithinAgeRange(
    List 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 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接口来替换CheckPerson。在这个接口中有一个boolean test(T t)方法:

interface Predicate {
  boolean test(T t);
}

Predicate接口是一个泛型接口。泛型类(或者是泛型接口)使用一对尖括号()指定了一个或多个类型参数。在这个接口中只有一个类型参数。在使用具体类声明或实例化一个泛型类时,就会获得一个参数化类。比如说参数化类Predicate就是这样的:

interface Predicate {
  boolean test(Person t);
}

在这个参数化类中有一个与CheckPerson.boolean test(Person p)方法的参数和返回值都一致的方法。因此就可以同如下方法所演示的一样使用Predicate接口来替换CheckPerson接口:

public static void printPersonsWithPredicate(
    List roster, Predicate 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作为参数类型时并没有显式指定参数类型。这里并不是唯一适用lambda表达式的地方,下面的方案会介绍更多lambda表达式的用法。

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

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

public static void printPersonsWithPredicate(
    List roster, Predicate 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,它有一个抽象方法void accept(T t)正好满足这个要求。在下面的代码中使用一个Consumer的实例调用accept方法替换了p.printPerson():

public static void processPersons(
    List roster,
    Predicate tester,
    Consumer 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就有一个这样的方法R apply(T t)。下面的方法从参数mapper中获取数据,并在这些数据上执行参数block指定的行为:

public static void processPersonsWithFunction(
    List roster,
    Predicate tester,
    Function mapper,
    Consumer 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  void processElements(
    Iterable source,
    Predicate tester,
    Function mapper,
    Consumer 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));

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

java中lambda表达式简单用例

表中的filter、map和forEach操作都是聚集操作。聚集操作处理的元素来自Stream,而非是直接从集合中获取(就是因为这示例程序中调用的第一个方法是stream())。Stream是一个数据序列。和集合不同,Stream并没有用特定的结构存储数据。相反的,Stream从一个特定的源获取数据,比如从集合获取数据,通过一个pipeline。pipeline是一个Stream操作序列,在这个例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表达式作为参数,这也给了我们许多自定义的空间。

更多java中lambda表达式简单用例相关文章请关注PHP中文网!

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang处理数据库错误教程合集
Golang处理数据库错误教程合集

本专题整合了Golang数据库错误处理方法、技巧、管理策略相关内容,阅读专题下面的文章了解更多详细内容。

61

2026.02.06

java多线程方法汇总
java多线程方法汇总

本专题整合了java多线程面试题、实现函数、执行并发相关内容,阅读专题下面的文章了解更多详细内容。

28

2026.02.06

1688阿里巴巴货源平台入口与批发采购指南
1688阿里巴巴货源平台入口与批发采购指南

本专题整理了1688阿里巴巴批发进货平台的最新入口地址与在线采购指南,帮助用户快速找到官方网站入口,了解如何进行批发采购、货源选择以及厂家直销等功能,提升采购效率与平台使用体验。

446

2026.02.06

快手网页版入口与电脑端使用指南 快手官方短视频观看入口
快手网页版入口与电脑端使用指南 快手官方短视频观看入口

本专题汇总了快手网页版的最新入口地址和电脑版使用方法,详细提供快手官网直接访问链接、网页端操作教程,以及如何无需下载安装直接观看短视频的方式,帮助用户轻松浏览和观看快手短视频内容。

258

2026.02.06

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

18

2026.02.06

Python 微服务架构与 FastAPI 框架
Python 微服务架构与 FastAPI 框架

本专题系统讲解 Python 微服务架构设计与 FastAPI 框架应用,涵盖 FastAPI 的快速开发、路由与依赖注入、数据模型验证、API 文档自动生成、OAuth2 与 JWT 身份验证、异步支持、部署与扩展等。通过实际案例,帮助学习者掌握 使用 FastAPI 构建高效、可扩展的微服务应用,提高服务响应速度与系统可维护性。

9

2026.02.06

JavaScript 异步编程与事件驱动架构
JavaScript 异步编程与事件驱动架构

本专题深入讲解 JavaScript 异步编程与事件驱动架构,涵盖 Promise、async/await、事件循环机制、回调函数、任务队列与微任务队列、以及如何设计高效的异步应用架构。通过多个实际示例,帮助开发者掌握 如何处理复杂异步操作,并利用事件驱动设计模式构建高效、响应式应用。

12

2026.02.06

java连接字符串方法汇总
java连接字符串方法汇总

本专题整合了java连接字符串教程合集,阅读专题下面的文章了解更多详细操作。

67

2026.02.05

java中fail含义
java中fail含义

本专题整合了java中fail的含义、作用相关内容,阅读专题下面的文章了解更多详细内容。

30

2026.02.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
AngularJS教程
AngularJS教程

共24课时 | 3.4万人学习

【李炎恢】ThinkPHP8.x 后端框架课程
【李炎恢】ThinkPHP8.x 后端框架课程

共50课时 | 4.6万人学习

php初学者入门课程
php初学者入门课程

共10课时 | 0.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号