如何快乐的使用 Java 8 的 Lambda

Java 8 的 Lambda 特性较之于先前的泛型加入更能鼓舞人心的,我对 Lambda 的理解是它得以让 Java 以函数式思维的方式来写代码。而写出的代码是否是函数式,并不单纯在包含了多少 Lambda 表达式,而在思维,要神似。

实际中看过一些代码,为了 Lambda 表达式而 Lambda(函数式),有一种少年不识愁滋味,为赋新词强说愁的味道。从而致使原本一个简单的方调用硬生生的要显式的用类如 apply(), accept(obj) 等形式。不仅造成代码可读性差,且可测试性也变坏了。

为什么说的是快乐的使用 Java 8 的 Lambda 呢?我窃以为第一个念头声明 Lambda 表达式为实例/类变量(像本文第一段代码那样),而不是方法的,一定会觉得如此使用方式很快乐的。所谓独乐乐,不如众乐乐;独乐乐,众不乐定然是更大的快乐; 更极致一些,不管什么时候必须是:我快乐,所以你也快乐。

一方面也在于 Java 还没有进化到 JavaScript 或  Scala 那样的水平,JavaScript 的函数类型变量,不一定要用 apply 或 call, 直接括号就能实现方法调用。Scala 的函数类型用括号调用也会自动匹配到 apply 或 update 等方法上去。

看下面的样本代码

上面的 fullName Lambda 表达式看起来就有点别扭,完全可以写成一个普通方法

那么调用起来只需要简单的

makeFullName(firstName, lastName)

那么此例中把简单方法写成一个 Lambda 表达式来调用有什么不友好之处呢?

  1. 不利于理解,Lambda 表达式的类型充斥着 Consumer, Function, BiFunction 等太宽泛的声明
  2. 参数类型与形参分离在表达式等号两边,不利于一一对应(右方重复一遍参数类型更不可取),真正的返回值也不明了
  3. 调用时更得多余的 get(), accept(obj), apply(obj1, obj2) 那样的方法
  4. 既然有逻辑,就应该有测试,Lambda 表达式虽是一个变量也不例如,测试时也不得用 apply 那样的调用
  5. Lambda 表达式为变量的形式,可能会随每一个对象实例有一单独的拷贝。当然声明为静态可以避免。
  6. 重构时更需大动干戈,比如前面的例子还要考虑 middleName 的情况,表达式要更动为

JDK 中还没有 TriFunction, 还得自己创造,不同数量的参数都得更新 Lambda 表达式的类型。如果是一个普通方法重构起来就方便多了,跟多一个人多一副碗筷一样。

 解释上面第 #5 条,对于方法,实现代码在 JVM 中只有一份,而 Lambda 实例变量如果不捕获外部变量的话,与方法是一样的,例如前面的 Account 为例

但是 Lambda 表达式需捕获外部变量时,例如

那么新建的两个 Account 对象的 fullName 属性就不是同一个了。因为 Lambda 需要捕获外部一个不确定的值,所以它也随宿主实例也变。

难道不应该用 Lambda 表达式变量,那倒不是,如果一个方法接受的是一个函数,如

那么是可以声明一个 Lambda 表达式变量,来传入。不过这种情况下用方法引用还是更方便些,方法的测试总是比 Lambda 表达式的测试容易。

个人习惯,一般需要 Lambda 表达式变量时基本是声明为局部变量,或是调用接受函数参数的方法时以内联的方法书写,像

对于使用方法引用方式的重构也不难,getName() 的参数类型变为 TriFunction, makeFullName() 方法再加一个参数就行, 调用形式仍然不变,还是

如果引用的方法是别人写的也不用慌,无须总去创建一样的方法签名来强型上方法引用,也可以和改 Lambda 实现代码一样的方式比改动,如下

本人希望的是,对函数的 apply(), accept(obj) 这样的显式调用应该是框架设计实现的职责,对框架使用者应该透明,或者说是隐藏背后的细节,只管传入需要的函数类型或方法引用。如果函数实现需要共享的话,写成方法更优于一个 Lambda 表达式,方法容易单独测试。特别是用 Mockito 捕获到了一个传入某个方法的 Lambda  表达式实例时,不那么好验证它的内部实现。

小结一下:

  1. 函数式思维最关键应该是 Data In, Data Out, 编程语言 Lambda 特性可以促使我们达成这一目的; 但不是代码中有了  Lambda 表达就是函数式风格。
  2. 其次代码的首先是人阅读,其次才是机器,所以它应该表达直截,明了,很强的可读性与可测试性。
  3. 具体讲如何快乐使用 Java 8 的 Lambda 呢,仅代表本人想法,可以用内联式,或方法引用,或局部的 Lambda 表达式变量,最后才是实例/类的 Lambda 表达式变量。

补充一个例子,在方法体中重复声明完全相同的不捕获任何外部变量的 Lambda 表达式都是新的实例

以上测试在 Java 8 平台上进行的。

本文链接 https://yanbin.blog/happy-with-java8-lambda/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

2 Comments
Inline Feedbacks
View all comments
wayne
wayne
5 years ago

发现面向对象习惯了,函数式编程能感觉到代码简洁,写起来快,不过带来到是代码看起来并不直观了,阅读理解成本提高了