Java 8 和 JSR 335:Lambda 表达式(第 1 部分)

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

这篇文章是关于 Lambda 表达式和流的两篇系列文章中的第一篇。 点击此处查看帖子的第二部分。

介绍

今天我要写一篇不一样的文章。这篇文章基于我专门讨论 Java 8 Lambda 表达式和流功能(在 JSR 335 中指定)的研讨会。

这篇文章的重点是对参加研讨会的每个人的研讨会内容进行总结,作为参考指南。对于那些没有零钱参加的人,我建议您看一下演示文稿(可作为 SpeakerDeck 获得)并在演示文稿后阅读这篇文章。演示文稿中使用的源代码可在 GitHub 上获得。

分享关于这些新功能的一些想法会很棒。我相信这是一种新的编程模式的出现,在接下来的几年里,我们将看到这些功能的许多新用例。

我会把这篇文章分成两部分。一个关于 Lambda 表达式 ,另一个关于

让我们开始吧!

拉姆达表达式

什么是 lambda 表达式?在 30 年代,Alonzo Church 创建了 Lambda 微积分 。能够表示任何 可计算函数的 正式系统 。我不会讨论 Lambda 微积分的细节,但它对我们来说有两个重要特征:

  1. 它具有单一的转换规则和函数定义方案
  2. 所有函数都是匿名的

在 JDK 8 中,我们称 Lambda 表达式为匿名函数的定义。下面是 Java 中 Lambda 表达式的一个简单示例:


 Consumer<String> consumer = (x) -> System.out.print(x);

定义之后,我可以调用方法 consumer.accept("Hello World!"); 它将字符串参数应用于正文 System.out.print(x) 。结果将是 "Hello World!" 控制台中打印的字符串。

我们可以认为它是一个定义为两部分的函数:

  1. 表达式的右边部分是 函数体
  2. 函数的签名 在函数式接口中定义,在本例中为 Consumer<T> 接口(下面更多关于函数式接口的信息)

一个重要的细节是 lambda 表达式与与其关联的函数接口属于同一类型

Lambda 表达式与匿名类

有很多关于 lambda 表达式和匿名类的讨论。这篇文章的目的不是揭开他们所有的共同点或不同点的神秘面纱。您只需要知道它们是不同的。 Lambda 表达式并不是编写匿名类的更简单方法。这并不意味着在某些情况下您不能使用 lambda 代替匿名类。看看这个用匿名类实现压缩的例子:


 Consumer<String> consumer = (x) -> System.out.print(x);

这是用 lambda 表达式实现的同一个比较器:


 Consumer<String> consumer = (x) -> System.out.print(x);

哪个更简单?我们将更多地讨论 lambda 表达式和匿名类之间的一些区别。最重要的是记住它们是不一样的。

现在让我们继续。我们将在本次研讨会中寻找更多有趣的 Lambda 表达式用法示例。

功能接口

现在我们了解了什么是 lambda 表达式,我们应该看看函数式接口。函数式接口的定义非常简单:


功能接口是具有 单个抽象方法 任何 接口。

这个定义中有两个重要的细节。第一个是关于单词 any 。这意味着功能接口的概念是向后兼容的,独立于 JDK 版本。

第二个细节是关于单个抽象方法的。从 JDK 8 开始,现在我们可以在我们的接口上定义 默认方法 ,一个默认实现用于接口的所有实现类,如果它们没有被它们覆盖的话。直到 JDK 7 实现这种行为通常我们创建一个抽象类来实现接口并定义方法的默认实现。

追赶:函数式接口是一个具有 一个抽象方法 (没有实现)和 零个或多个默认方法的 接口。

下面的例子展示了一个简单的功能接口的例子。 @FunctionalInterface 注释是可选的。


 Consumer<String> consumer = (x) -> System.out.print(x);

现在让我们仔细看看默认方法以及它们的行为方式......

默认方法

正如我所说(写 :P)默认方法是一种在接口上添加新方法而不强制每个实现类实现此方法的方法。此功能的重点是向后兼容性。

默认方法的 方法解析 与普通继承一样,选择最接近的方法。如果一个类实现了两个默认方法名称冲突的接口(方法解析冲突),则该类必须覆盖默认方法。如果该类需要引用特定的默认方法,它可以使用 Interface.super.defaultMethodName(); .

下面是解决冲突并仍然使用两个接口的默认实现的示例:


 Consumer<String> consumer = (x) -> System.out.print(x);

关于默认方法的一个重要细节是它们可以访问 this 。如果不注意使用,这可能会导致一些麻烦:


 Consumer<String> consumer = (x) -> System.out.print(x);

无论你调用 method1() 还是 method2() 你都会得到一个 StackOverflowException!!!

变量捕获

Lambda 表达式可以与其主体外部的变量交互。

lambda 表达式中使用的局部变量必须是 final 实际上是 final 。后者意味着一个没有改变的变量(编译器可以推断它是最终的)。对于其他变量范围,匿名类的相同规则适用。

lambda 表达式和匿名内部类之间的一个很大区别是 this 关键字的使用。在匿名类内部使用它指的是匿名类。但 在 lambda 表达式内部, this 引用外部对象

方法参考

Lambda 表达式是一种定义匿名函数的方法,但您是否喜欢在每次需要时都重写一个函数?这就是另一个 JDK 8 功能的优势所在,即 方法参考

使用方法引用,我们可以在需要 lambda 表达式的地方使用现有函数。唯一的限制(这很有意义)是引用方法的签名必须与功能接口的签名相匹配。您甚至可以引用 构造函数 实例方法

让我向您展示一个方法参考的例子。还记得本文中的第一个 lambda 表达式吗?让我们稍微改变一下:


 Consumer<String> consumer = (x) -> System.out.print(x);

你认为当我们调用 consumer.accept("Hello World!");

好吧,编译器会看到您正在引用 System.out public void print(String s) 方法,因此它匹配参数并知道如何执行该方法。您不必明确告诉编译器参数是什么。这等于说: “我想使用功能接口中定义的签名,并且对于实现,我希望您使用我所指的函数的主体” 。正如预期的那样,结果将是 "Hello World!" 控制台中打印的字符串。

结论

lambda 表达式是 Java 语言中迄今为止最激动人心的变化之一,我很高兴看到从中出现的用例和模式。它还有助于避免匿名类的冗长。

这篇文章的第一部分到此结束。我希望它有助于理解这些功能和底层概念。在下一部分中,我们将讨论对支持顺序和并行聚合操作(也称为流)的元素序列的操作。

到时候那里见!

参考