这篇文章主要介绍了 Java 函数式编程(一): 你好, Lambda 表达式, 本文讲解了新老函数式编程的一些变化, 需要的朋友可以参考下
第一章 你好, lambda 表达式!
第一节
Java 的编码风格正面临着翻天覆地的变化
我们每天的工作将会变成更简单方便, 更富表现力 Java 这种新的编程方式早在数十年前就已经出现在别的编程语言里面了这些新特性引入 Java 后, 我们可以写出更简洁, 优雅, 表达性更强, 错误更少的代码我们可以用更少的代码来实现各种策略和设计模式
在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程在使用这种全新的优雅的方式进行设计编码之前, 我们先来看下它到底好在哪里
改变了你的思考方式
命令式风格 Java 语言从诞生之初就一直提供的是这种方式使用这种风格的话, 我们得告诉 Java 每一步要做什么, 然后看着它切实的一步步执行下去这样做当然很好, 就是显得有点初级代码看起来有点啰嗦, 我们希望这个语言能变得稍微智能一点; 我们应该直接告诉它我们想要什么, 而不是告诉它如何去做好在现在 Java 终于可以帮我们实现这个愿望了我们先来看几个例子, 了解下这种风格的优点和不同之处
正常的方式
我们先从两个熟悉的例子来开始这是用命令的方式来查看芝加哥是不是指定的城市集合里记住, 本书中列出的代码只是部分片段而已
- boolean found = false;
- for (String city: cities) {
- if (city.equals("Chicago")) {
- found = true;
- break;
- }
- }
- System.out.println("Found chicago?:" + found);
这个命令式的版本看起来有点啰嗦而且初级; 它分成好几个执行部分先是初始化一个叫 found 的布尔标记, 然后遍历集合里的每一个元素; 如果发现我们要找的城市了, 设置下这个标记, 然后跳出循环体; 最后打印出查找的结果
一种更好的方式
细心的 Java 程序员看完这段代码后, 很快会想到一种更简洁明了的方式, 就像这样:
System.out.println("Found chicago?:" + cities.contains("Chicago"));
这也是一种命令式风格的写法 contains 方法直接就帮我们搞定了
实际改进的地方
代码这么写有这几个好处:
1. 不用再捣鼓那个可变的变量了
2. 将迭代封装到了底层
3. 代码更简洁
4. 代码更清晰, 更聚焦
5. 少走弯路, 代码和业务需求结合更密切
6. 不易出错
7. 易于理解和维护
来个复杂点的例子
这个例子太简单了, 命令式查询一个元素是否存在于某个集合在 Java 里随处可见现在假设我们要用命令式编程来进行些更高级的操作, 比如解析文件 , 和数据库交互, 调用 web 服务, 并发编程等等现在我们用 Java 可以写出更简洁优雅同时出错更少的代码, 更不只是这种简单的场景
老的方式
我们来看下另一个例子我们定义了一系列价格, 并通过不同的方式来计算打折后的总价
- final List<BigDecimal> prices = Arrays.asList(
- new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"),
- new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"),
- new BigDecimal("45"), new BigDecimal("12"));
假设超过 20 块的话要打九折, 我们先用普通的方式实现一遍
- BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO;
- for(BigDecimal price : prices) {
- if(price.compareTo(BigDecimal.valueOf(20)) > 0)
- totalOfDiscountedPrices =
- totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9)));
- }
- System.out.println("Total of discounted prices:" + totalOfDiscountedPrices);
这个代码很熟悉吧; 先用一个变量来存储总价; 然后遍历所有的价格, 找出大于 20 块的, 算出它们的折扣价, 并加到总价里面; 最后打印出折扣后的总价
下面是程序的输出:
Total of discounted prices: 67.5
结果完全正确, 不过这样的代码有点乱这并不是我们的错, 我们只能用已有的方式来写不过这样的代码实在有点初级, 它不仅存在基本类型偏执, 而且还违反了单一职责原则如果你是在家工作并且家里还有想当码农的小孩的话, 你可得把你的代码藏好了, 万一他们看见了会很失望地叹气道, 你是靠这些玩意儿糊口的?
还有更好的方式
我们还能做的更好并且要好很多我们的代码有点像需求规范这样能缩小业务需求和实现的代码之间的差距, 减少了需求被误读的可能性
我们不再让 Java 去创建一个变量然后没完没了的给它赋值了, 我们要从一个更高层次的抽象去与它沟通, 就像下面的这段代码
- final BigDecimal totalOfDiscountedPrices =
- prices.stream()
- .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
- .map(price -> price.multiply(BigDecimal.valueOf(0.9)))
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- System.out.println("Total of discounted prices:" + totalOfDiscountedPrices);
大声的读出来吧过滤出大于 20 块的价格, 把它们转化成折扣价, 然后加起来这段代码和我们描述需求的流程简直一模一样 Java 里还可以很方便的把一行长的代码折叠起来, 根据方法名前面的点号进行按行对齐, 就像上面那样
代码非常简洁, 不过我们用到了 Java8 里面的很多新东西首先, 我们调用 了价格列表的一个 stream 方法这打开了一扇大门, 门后边有数不尽的便捷的迭代器, 这个我们在后面会继续讨论
我们用了一些特殊的方法, 比如 filter 和 map, 而不是直接的遍历整个列表这些方法不像我们以前用的 JDK 里面的那些, 它们接受一个匿名的函数 lambda 表达式作为参数 (后面我们会深入的展开讨论) 我们调用 reduce()方法来计算 map()方法返回的价格的总和
就像 contains 方法那样, 循环体被隐藏起来了不过 map 方法 (以及 filter 方法) 则更复杂得多 它对价格列表中的每一个价格, 调用了传进来的 lambda 表达式进行计算, 把结果放到一个新的集合里面最后我们在这个新的集合上调用 reduce 方法得出最终的结果
这是以上代码的输出结果:
Total of discounted prices: 67.5
改进的地方
这和前面的实现相比改进明显:
1. 结构良好而不混乱
2. 没有低级操作
3. 易于增强或者修改逻辑
4. 由方法库来进行迭代
5. 高效; 循环体惰性求值
6. 易于并行化
下面我们会说到 Java 是如何实现这些的
lambda 表达式来拯救世界了
lambda 表达式是让我们远离命令式编程烦恼的快捷键 Java 提供的这个新特性, 改变了我们原有的编程方式, 使得我们写出的代码不仅简洁优雅, 不易出错, 而且效率更高, 易于优化改进和并行化
第二节: 函数式编程的最大收获
函数式风格的代码有更高的信噪比; 写的代码更少了, 但每一行或者每个表达式做的却更多了比命令式编程相比, 函数式编程让我们获益良多:
避免了对变量的显式的修改或赋值, 这些通常是 BUG 的根源, 并导致代码很难并行化在命令行编程中我们在循环体内不停的对 totalOfDiscountedPrices 变量赋值在函数式风格里, 代码不再出现显式的修改操作变量修改的越少, 代码的 BUG 就越少
函数式风格的代码可以轻松的实现并行化如果计算很费时, 我们可以很容易让列表中的元素并发的执行如果我们想把命令式的代码并行化, 我们还得担心并发修改 totalOfDiscountedPrices 变量带来的问题在函数式编程中我们只会在完全处理完后才访问这个变量, 这样就消除了线程安全的隐患
代码的表达性更强命令式编程要分成好几个步骤要说明要做什么创建一个初始化的值, 遍历价格, 把折扣价加到变量上等等而函数式的话只需要让列表的 map 方法返回一个包括折扣价的新的列表然后进行累加就可以了
函数式编程更简洁; 和命令式相比同样的结果只需要更少的代码就能完成代码更简洁意味着写的代码少了, 读的也少了, 维护的也少了看下第 7 页的 "简洁少就是简洁了吗"
函数式的代码更直观读代码就像描述问题一样一旦我们熟悉语法后就很容易能看懂 map 方法对集合的每个元素都执行了一遍给定的函数(计算折扣价), 然后返回结果集, 就像下图演示的这样
图 1map 对集合中的每个元素执行给定的函数
有了 lambda 表达式之后, 我们可以在 Java 里充分发挥函数式编程的威力使用函数式风格, 就能写出表达性更佳, 更简洁, 赋值操作更少, 错误更少的代码了
支持面向对象编程是 Java 一个主要的优点函数式编程和面向对象编程并不排斥真正的风格变化是从命令行编程转到声明式编程在 Java 8 里, 函数式和面向对象可以有效的融合到一起我们可以继续用 OOP 的风格来对领域实体以及它们的状态, 关系进行建模除此之外, 我们还可以对行为或者状态的转变, 工作流和数据处理用函数来进行建模, 建立复合函数
第三节: 为什么要用函数式风格?
我们看到了函数式编程的各项优点, 不过使用这种新的风格划得来吗? 这只是个小改进还是说换头换面? 在真正在这上面花费工夫前, 还有很多现实的问题需要解答
小明问到:
代码少就是简洁了吗?
简洁是少而不乱, 归根结底是说要能有效的表达意图它带来的好处意义深远
写代码就好像把配料堆到一起, 简洁就是说能把配料调成调料要写出简洁的代码可得下得狠工夫读的代码是少了, 真正有用的代码对你是透明的一段很难理解或者隐藏细节的短代码只能说是简短而不是简洁
简洁的代码竟味着敏捷的设计简洁的代码少了那些繁文缛节这是说我们可以对想法进行快速尝试, 如果不错就继续, 如果效果不佳就迅速跳过
用 Java 写代码并不难, 语法简单而且我们也已经对现有的库和 API 很了如指掌了真正难的是要拿它来开发和维护企业级的应用
我们要确保同事在正确的时间关闭了数据库连接, 还有他们不会不停的占有事务, 能在合适的分层上正确的处理好异常, 能正确的获得和释放锁, 等等
这些问题任何一个单独来看都不是什么大事不过如果和领域内的复杂性一结合的话, 问题就变得很棘手了, 开发资源紧张, 难以维护
如果把这些策略封装成许多小块的代码, 让它们各自进行约束管理的话, 会怎么样呢? 那我们就不用再不停的花费精力去实施策略了这是个巨大的改进, 我们来看下函数式编程是如何做到的
疯狂的迭代
我们一直都在写各种迭代来处理列表, 集合, 还有 map 在 Java 里使用迭代器再常见不过了, 不过这太复杂了它们不仅占用了好几行代码, 还很难进行封装
我们是如何遍历集合并打印它们的? 可以使用一个 for 循环我们怎么从集合里过滤出一些元素? 还是用 for 循环, 不过还需要额外增加一些可修改的变量选出了这些值后, 怎么用它们求出最终值, 比如最小值, 最大值, 平均值之类的? 那还得再循环, 再修改变量
这样的迭代就是个万金油, 啥都会点, 但样样稀松现在 Java 为许多操作都专门提供了内建的迭代器: 比如只做循环的, 还有做 map 操作的, 过滤值的, 做 reduce 操作的, 还有许多方便的函数比如 最大最小值, 平均值等等除此之外, 这些操作还可以很好的组合起来, 因此我们可以将它们拼装到一起来实现业务逻辑, 这样做既简单代码量也少而且写出来的代码可读性强, 因为它从逻辑上和描述问题的顺序是一致的我们在第二章, 集合的使用, 第 19 页会看到几个这样的例子, 这本书里这样的例子也比比皆是
应用策略
策略贯穿于整个企业级应用中比如, 我们需要确认某个操作已经正确的进行了安全认证, 我们要保证事务能够快速执行, 并且正确的更新修改日志这些任务通常最后就变成服务端的一段普通的代码, 就跟下面这个伪代码差不多:
- Transaction transaction = getFromTransactionFactory();
- //... operation to run within the transaction ...
- checkProgressAndCommitOrRollbackTransaction();
- UpdateAuditTrail();
这种处理方法有两个问题首先, 它通常导致了重复的工作量并且还增加了维护的成本第二, 很容易忘了业务代码中可能会被抛出来的异常, 可能会影响到事务的生命周期和修改日志的更新这里应该使用 try, finally 块来实现, 不过每当有人动了这块代码, 我们又得重新确认这个策略没有被破坏
还有一种方法, 我们可以去掉工厂, 把这段代码放在它前面不用再获取事务对象, 而是把执行的代码传给一个维护良好的函数, 就像这样:
- runWithinTransaction((Transaction transaction) -> {
- //... operation to run within the transaction ...
- });
这是你的一小步, 但是省了一大堆事检查状态同时更新日志的这个策略被抽象出来封装到了 runWithinTransaction 方法里我们给这个方法发送一段需要在事务上下文里运行的代码我们不用再担心谁忘了执行这个步骤或者没有处理好异常这个实施策略的函数已经把这事搞定了
我们将会在第五章中介绍如果使用 lambda 表达式来应用这样的策略
扩展策略
策略看起来无处不在除了要应用它们外, 企业级应用还需要对它们进行扩展我们希望能通过一些配置信息来增加或者删除一些操作, 换言之, 就是能在模块的核心逻辑执行前进行处理这在 Java 里很常见, 不过需要预先考虑到并设计好
需要扩展的组件通常有一个或者多个接口我们需要仔细设计接口以及实现类的分层结构这样做可能效果很好, 但是会留下一大堆需要维护的接口和类这样的设计很容易变得笨重且难以维护, 最终破坏扩展的初衷
还有一种解决方法函数式接口, 以及 lambda 表达式, 我们可以用它们来设计可扩展的策略我们不用非得创建新的接口或者都遵循同一个方法名, 可以更聚焦要实现的业务逻辑, 我们会在 73 页的使用 lambda 表达式进行装饰中提到
轻松实现并发
一个大型应用快到了发布里程碑的时候, 突然一个严重的性能问题浮出水面团队迅速定位出性能瓶颈点是出在一个处理海量数据的庞大的模块里团队中有人建议说如果能充分发掘多核的优势的话可以提高系统性能不过如果这个庞大的模块是用老的 Java 风格写的话, 刚才这个建议带来的喜悦很快就破灭了
团队很快意识到要这把这个庞然大物从串行执行改成并行需要费很大的精力, 增加了额外的复杂度, 还容易引起多线程相关的 BUG 难道没有一种提高性能的更好方式吗?
有没有可能串行和并行的代码都是一样的, 不管选择串行还是并行执行, 就像按一下开关, 表明一下想法就可以了?
听起来好像只有纳尼亚里面能这样, 不过如果我们完全用函数式进行开发的话, 这一切都将成为现实内置的迭代器和函数式风格将扫清通往并行化的最后一道障碍 JDK 的设计使得串行和并行执行的切换只需要一点不起眼的代码改动就可以实现, 我们将会在 145 页完成并行化的飞跃中提到
讲故事
在业务需求变成代码实现的过程中会丢失大量的东西丢失的越多, 出错的可能性和管理的成本就越高如果代码看起来就跟描述需求一样, 将会很方便阅读, 和需求人员讨论也变的更简单, 也更容易满足他们的需求
比如你听到产品经理在说, 拿到所有股票的价格, 找出价格大于 500 块的, 计算出能分红的资产总和使用 Java 提供的新设施, 可以这么写:
tickers.map(StockUtil: :getprice).filter(StockUtil: :priceIsLessThan500).sum()
这个转化过程几乎是无损的, 因为基本上也没什么要转化的这是函数式在发挥作用, 在本书中还会看到更多这样的例子, 尤其是第 8 章, 使用 lambda 表达式来构建程序, 137 页
关注隔离
在系统开发中, 核心业务和它所需要的细粒度逻辑通常需要进行隔离比如说, 一个订单处理系统想要对不同的交易来源使用不同的计税策略把计税和其余的处理逻辑进行隔离会使得代码重用性和扩展性更高
在面向对象编程中我们把这个称之为关注隔离, 通常用策略模式来解决这个问题解决方法一般就是创建一些接口和实现类
我们可以用更少的代码来完成同样的效果我们还可以快速尝试自己的产品思路, 而不用上来就得搞出一堆代码, 停滞不前我们将在 63 页的, 使用 lambda 表达式进行关注隔离中进一步探讨如果通过轻量级函数来创建这种模式以及进行关注隔离
惰性求值
开发企业级应用时, 我们可能会与 WEB 服务进行交互, 调用数据库, 处理 XML 等等我们要执行的操作有很多, 不过并不是所有时候都全部需要避免某些操作或者至少延迟一些暂时不需要的操作是提高性能或者减少程序启动, 响应时间的一个最简单的方式
这只是个小事, 但用纯 OOP 的方式来实现还需要费一番工夫为了延迟一些重量级对象的初始化, 我们要处理各种对象引用 , 检查空指针等等
不过, 如果使用了新的 Optinal 类和它提供的一些函数式风格的 API, 这个过程将变得很简单, 代码也更清晰明了, 我们会在 105 页的延迟初始化中讨论这个
提高可测性
代码的处理逻辑越少, 容易被改错的地方当然也越少一般来说函数式的代码比较容易修改, 测试起来也较简单
另外, 就像第 4 章, 使用 lambda 表达式进行设计和第 5 章资源的使用中那样, lambda 表达式可以作为一种轻量级的 mock 对象, 让异常测试变得更清晰易懂 lambda 表达式还可以作为一个很好的测试辅助工具很多常见的测试用例都可以接受并处理 lambda 表达式这样写的测试用例能够抓住需要回归测试的功能的本质同时, 需要测试的各种实现都可以通过传入不同的 lambda 表达式来完成
JDK 自己的自动化测试用例也是 lambda 表达式的一个很好的应用范例想了解更多的话可以看下 OpenJDK 仓库里的源代码通过这些测试程序可以看到 lambda 表达式是如何将测试用例的关键行为进行参数化; 比如, 它们是这样构建测试程序的, 新建一个结果的容器, 然后对一些参数化的后置条件进行检查
我们已经看到, 函数式编程不仅能让我们写出高质量的代码, 还能优雅的解决开发过程中的各种难题这就是说, 开发程序将变得更快更简单, 出错也更少只要你能遵守我们后面将要介绍到的几条准则
第四节: 进化而非革命
我们用不着转向别的语言, 就能享受函数式编程带来的好处; 需要改变的只是使用 Java 的一些方式 C++,Java,C# 这些语言都支持命令式和面向对象的编程不过现在它们都开始投入函数式编程的怀抱里了我们刚才已经看到了这两种风格的代码, 并讨论了函数式编程能带来的好处现在我们来看下它的一些关键概念和例子来帮助我们学习这种新的风格
Java 语言的开发团队花费了大量的时间和精力把函数式编程的能力添加到了 Java 语言和 JDK 里要享受它带来的好处, 我们得先介绍几个新的概念我们只要遵循下面几条规则就能提升我们的代码质量:
1. 声明式
2. 提倡不可变性
3. 避免副作用
4. 优先使用表达式而不是语句
5. 使用高阶函数进行设计
我们来看下这几条实践准则
声明式
我们所熟悉的命令式编程的核心就是可变性和命令驱动的编程我们创建变量, 然后不断修改它们的值我们还提供了要执行的详细的指令, 比如生成迭代的索引标志, 增加它的值, 检查循环是否结束, 更新数组的第 N 个元素等在过去由于工具的特性和硬件的限制, 我们只能这么写代码 我们也看到了, 在一个不可变集合上, 声明式的 contains 方法比命令式的更容易使用所有的难题和低级的操作都在库函数里来实现了, 我们不用再关心这些细节就冲着简单这点, 我们也应该使用声明式编程不可变性和声明式编程是函数式编程的精髓, 现在 Java 终于把它变成了现实
提倡不可变性
变量可变的代码会有很多活动路径改的东西越多, 越容易破坏原有的结构, 并引入更多的错误有多个变量被修改的代码难于理解也很难进行并行化不可变性从根本上消除了这些烦恼 Java 支持不可变性但没有强制要求但我们可以我们需要改变修改对象状态这个旧习惯我们要尽可能的使用不可变的对象 声明变量, 成员和参数的时候, 尽量声明为 final 的, 就像 Joshua Bloch 在 Effective Java 里说的那句名言那样, 把对象当成不可变的吧 当创建对象的时候, 尽量创建不可变的对象, 比如 String 这样的创建集合的时候, 也尽量创建不可变或者无法修改的集合, 比如用 Arrays.asList()和 Collections 的 unmodifiableList()这样的方法 避免了可变性我们才可以写出纯粹的函数也就是, 没有副作用的函数
避免副作用
假设你在写一段代码到网上去抓取一支股票的价格然后写到一个共享变量里如果我们有很多价格要抓取, 我们得串行的执行这些费时的操作如果我们想借助多线程的能力, 我们得处理线程和同步带来的麻烦事, 防止出现竞争条件最后的结果是程序的性能很差, 为了维护线程而废寝忘食如果消除了副作用, 我们完全可以避免这些问题 没有副作用的函数推崇的是不可变性, 在它的作用域内不会修改任何输入或者别的东西这种函数可读性强, 错误少, 容易优化由于没有副作用, 也不用再担心什么竞争条件或者并发修改了不仅如此, 我们还可以很容易并行执行这些函数, 我们将在 145 页的来讨论这个
优先使用表达式
语句是个烫手的山芋, 因为它强制进行修改表达式提升了不可变性和函数组合的能力比如, 我们先用 for 语句计算折扣后的总价这样的代码导致了可变性以及冗长的代码使用 map 和 sum 方法的表达性更强的声明式的版本后, 不仅避免了修改操作, 同时还能把函数串联起来 写代码的时候应该尽量使用表达式, 而不是语句这样使得代码更简洁易懂代码会顺着业务逻辑执行, 就像我们描述问题的时候那样如果需求变动, 简洁的版本无疑更容易修改
使用高阶函数进行设计
Java 不像 Haskell 那些函数式语言那样强制要求不可变, 它允许我们修改变量因此, Java 不是, 也永远不会是, 一个纯粹的函数式编程语言然而, 我们可以在 Java 里使用高阶函数进行函数式编程 高阶函数使得重用更上一层楼有了高阶函数我们可以很方便的重用那些小而专, 内聚性强的成熟的代码 在 OOP 中我们习惯了给方法传递给对象, 在方法里面创建新的对象, 然后返回对象高阶函数对函数做的事情就跟方法对对象做的一样有了高阶函数我们可以
1. 把函数传给函数
2. 在函数内创建新的函数
3. 在函数内返回函数
我们已经见过一个把函数传参给另一个函数的例子了, 在后面我们还会看到创建函数和返回函数的示例我们先再看一遍把函数传参给函数的那个例子:
- prices.stream()
- .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9)))
- report erratum discuss
- .reduce(BigDecimal.ZERO, BigDecimal::add);
在这段代码中我们把函数 price -> price.multiply(BigDecimal.valueOf(0.9)), 传给了 map 函数传递的这个函数是在调用高阶函数 map 的时候才创建的通常来说一个函数有函数体, 函数名, 参数列表, 返回值这个实时创建的函数有一个参数列表后面跟着一个箭头(->), 然后就是很短的一段函数体了参数的类型由 Java 编译器来进行推导, 返回的类型也是隐式的这是个匿名函数, 它没有名字不过我们不叫它匿名函数, 我们称之为 lambda 表达式 匿名函数作为传参在 Java 并不算是什么新鲜事; 我们之前也经常传递匿名内部类即使匿名类只有一个方法, 我们还是得走一遍创建类的仪式, 然后对它进行实例化有了 lambda 表达式我们可以享受轻量级的语法了不仅如此, 我们之前总是习惯把一些概念抽象成各种对象, 现在我们可以将一些行为抽象成 lambda 表达式了 用这种编码风格进行程序设计还是需要费些脑筋的我们得把已经根深蒂固的命令式思维转变成函数式的开始的时候可能有点痛苦, 不过很快你就会习惯它了, 随着不断的深入, 那些非函数式的 API 逐渐就被抛到脑后了 这个话题就先到这吧, 我们来看看 Java 是如何处理 lambda 表达式的我们之前总是把对象传给方法, 现在我们可以把函数存储起来并传递它们 我们来看下 Java 能够将函数作为参数背后的秘密
第五节: 加了点语法糖
用 Java 原有的功能也是可以实现这些的, 不过 lambda 表达式加了点语法糖, 省掉了一些步骤, 使我们的工作更简单了这样写出的代码不仅开发更快, 也更能表达我们的想法 过去我们用的很多接口都只有一个方法: 像 Runnable, Callable 等等这些接口在 JDK 库中随处可见, 使用它们的地方通常用一个函数就能搞定原来的这些只需要一个单方法接口的库函数现在可以传递轻量级函数了, 多亏了这个通过函数式接口提供的语法糖 函数式接口是只有一个抽象方法的接口再看下那些只有一个方法的接口, Runnable,Callable 等, 都适用这个定义 JDK8 里面有更多这类的接口 Function, Predicate, Comsumer, Supplier 等 (157 页, 附录 1 有更详细的接口列表) 函数式接口可以有多个 static 方法, 和 default 方法, 这些方法是在接口里面实现的 我们可以用 @FunctionalInterface 注解来标注一个函数式接口编译器不使用这个注解, 不过有了它可以更明确的标识这个接口的类型不止如此, 如果我们用这个注解标注了一个接口, 编译器会强制校验它是否符合函数式接口的规则 如果一个方法接收函数式接口作为参数, 我们可以传递的参数包括:
1. 匿名内部类, 最古老的方式
2.lambda 表达式, 就像我们在 map 方法里那样
3. 方法或者构造器的引用(后面我们会讲到)
如果方法的参数是函数式接口的话, 编译器会很乐意接受 lambda 表达式或者方法引用作为参数 如果我们把一个 lambda 表达式传递给一个方法, 编译器会先把这个表达式转化成对应的函数式接口的一个实例这个转化可不止是生成一个内部类而已同步生成的这个实例的方法对应于参数的函数式接口的抽象方法比如, map 方法接收函数式接口 Function 作为参数在调用 map 方法时, java 编译器会同步生成它, 就像下图所示的一样
lambda 表达式的参数必须和接口的抽象方法的参数匹配这个生成的方法将返回 lambda 表达式的结果如果返回类型不直接匹配抽象方法的话, 这个方法会把返回值转化成合适的类型 我们已经大概了解了下 lambda 表达式是如何传递给方法的我们先来快速回顾一下刚讲的内容, 然后开始我们 lambda 表达式的探索之旅
总结
这是 Java 一个全新的领域通过高阶函数, 我们现在可以写出优雅流利的函数式风格的代码了这样写出的代码, 简洁易懂, 错误少, 利于维护和并行化 Java 编译器发挥了它的魔力, 在接收函数式接口参数的地方, 我们可以传入 lambda 表达式或者方法引用 我们现在可以进入 lambda 表达式以及为之改造的 JDK 库的世界来感觉它们的乐趣了在下一章中, 我们将从编程里面最常见的集合操作开始, 发挥 lambda 表达式的威力
来源: http://www.phperz.com/article/16/0716/235772.html