内容简介:Java 8 的 Lambda 特性较之于先前的泛型加入更能鼓舞人心的,我对 Lambda 的理解是它得以让 Java 以函数式思维的方式来写代码。而写出的代码是否是函数式,并不单纯在包含了多少 Lambda 表达式,而在思维,要神似。实际中看过一些代码,为了 Lambda 表达式而 Lambda(函数式),有一种少年不识愁滋味,为赋新词强说愁的味道。从而致使原本一个简单的方调用硬生生的要显式的用类如为什么说的是快乐的使用 Java 8 的 Lambda 呢?我窃以为会首先声明 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
等方法上去。
看下面的样本代码
public class Account { public BiFunction<String, String, String> fullName = (firstName, lastName) -> { //some logic, i.e. logics of fullName in different countries return firstName + " " + lastName; }; public String getName() { String firstName = "Speaker"; String lastName = "Wolf"; return fullName.apply(firstName, lastName); } }
上面的 fullName Lambda 表达式看起来就有点别扭,完全可以写成一个普通方法
public String makeFullName(String firstName, String lastName) { //return something with logics }
那么调用起来只需要简单的
makeFullName(firstName, lastName)
那么此例中把简单方法写成一个 Lambda 表达式来调用有什么不友好之处呢?
-
不利于理解,Lambda 表达式的类型充斥着
Consumer
,Function
,BiFunction
等太宽泛的声明 - 参数类型与形参分离在表达式等号两边,不利于一一对应(右方重复一遍参数类型更不可取),真正的返回值也不明了
-
调用时更得多余的
get()
,accept(obj)
,apply(obj1, obj2)
那样的方法 -
既然有逻辑,就应该有测试,Lambda 表达式虽是一个变量也不例如,测试时也不得用
apply
那样的调用 - Lambda 表达式为变量的形式,可能会随每一个对象实例有一单独的拷贝。当然声明为静态可以避免。
- 重构时更需大动干戈,比如前面的例子还要考虑 middleName 的情况,表达式要更动为
public TriFunction<String, String, String, String> fullName = (firstName, middleName, lastName) -> { //....... }
JDK 中还没有 TriFunction
, 还得自己创造,不同数量的参数都得更新 Lambda 表达式的类型。如果是一个普通方法重构起来就方便多了,跟多一个人多一副碗筷一样。
解释上面第 #5 条,对于方法,实现代码在 JVM 中只有一份,而 Lambda 实例变量如果不捕获外部变量的话,与方法是一样的,例如前面的 Account 为例
Account account1 = new Account(); Account account2 = new Account(); System.out.println(account1.fullName == account2.fullName); //true
但是 Lambda 表达式需捕获外部变量时,例如
private String suffix = "Sir"; public BiFunction<String, String, String> fullName = (firstName, lastName) -> { return firstName + " " + lastName + " " + suffix; }; ....... Account account1 = new Account(); Account account2 = new Account(); account1.fullName == account2.fullName; //就是 false 了, 而如果 suffix 是一个静态的变量时这个等式又是 true 了
那么新建的两个 Account 对象的 fullName 属性就不是同一个了。
难道不应该用 Lambda 表达式变量,那倒不是,如果一个方法接受的是一个函数,如
public String getName(BiFunction<String, String, String> builder) { return builder.apply(firstName, lastName); }
那么是可以声明一个 Lambda 表达式变量,来传入。不过这种情况下用方法引用还是更方便些,方法的测试总是比 Lambda 表达式的测试容易。
String name = getName(this::makeFullName);
个人习惯,一般需要 Lambda 表达式变量时基本是声明为局部变量,或是调用接受函数参数的方法时以内联的方法书写,像
String name = getName((firstName, lastName) -> { //logics return ...... });
对于使用方法引用方式的重构也不难, getName()
的参数类型变为 TriFunction
, makeFullName()
方法再加一个参数就行, 调用形式仍然不变,还是
String name = getName(this::makeFullName);
如果引用的方法是别人写的也不用慌,无须总去创建一样的方法签名来强型上方法引用,也可以和改 Lambda 实现代码一样的方式比改动,如下
String name = getName((firstName, lastName) -> makeFullName(firstName, lastName) + " " + suffix )
本人希望的是,对函数的 apply()
, accept(obj)
这样的显式调用应该是框架设计实现的职责,对框架使用者应该透明,或者说是隐藏背后的细节,只管传入需要的函数类型或方法引用。如果函数实现需要共享的话,写成方法更优于一个 Lambda 表达式,方法容易单独测试。特别是用 Mockito 捕获到了一个传入某个方法的 Lambda 表达式实例时,不那么好验证它的内部实现。
小结一下:函数式思维最关键应该是 Data In, Data Out
, 编程语言 Lambda 特性可以促使我们达成这一目的; 但不是代码中有了 Lambda 表达就是函数式风格。其次代码的首先是人阅读,其次才是机器,所以它应该表达直截,明了,很强的可读性与可测试性。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- RecyclerView使用指南(一)—— 基本使用
- 如何使用Meteorjs使用URL参数
- 使用 defer 还是不使用 defer?
- 使用 Typescript 加强 Vuex 使用体验
- [译] 何时使用 Rust?何时使用 Go?
- UDP协议的正确使用场合(谨慎使用)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
VC++深入详解
孙鑫 / 电子工业出版社 / 2006-6 / 89.00元
《VC++深入详解》主要从程序内部运行的机制和MFC程序的组织脉络入手,使读者在学习VC++编程知识时,既能够知其然,又能知其所以然,从而帮助读者从根本上理解和掌握Windows的程序设计。另外,《VC++深入详解》还贯穿作者多年来学习编程的一些经验,以及一些学习方法的建议,为读者进一步的学习提供指导。 《VC++深入详解》从实际应用入手,由浅入深、循序渐进地讲述Windows程......一起来看看 《VC++深入详解》 这本书的介绍吧!