java java8初探lambda表达式

lambda表达式的示例

参考:http://blog.csdn.net/bitcarmanlee/article/details/70195403

 

一、为什么使用lambda表达式

在Java 8中,引入了lambda表达式,从而支持函数式编程(关于什么是函数式编程,和面向对象比较有什么优缺点,将在以后的文章中提到)。

lambda表达式允许我们将行为传到函数里。

重点是“将行为传入函数”。

在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名内部类(关于匿名内部类,可以参考之前的文章,这里的例子也是上一篇文章的)。

http://www.xie4ever.com/2017/06/03/java-%E5%8C%BF%E5%90%8D%E5%86%85%E9%83%A8%E7%B1%BB/

在这里的例子中,面试官想检验面试者的年龄和工作年数,需要声明一个匿名内部类Arithmetic,定义一个checkYears方法,用其检查interviewee对象:

Interviewer.java

模拟一下review这段代码的过程:

首先从main方法开始,看到了调用check方法。

到了这一步,我们的关注点很可能就到了check方法的参数上。你会注意到这里使用了一个匿名内部类,匿名类中有一个checkYears方法,对传入的interviewee对象的属性进行校验。

问题是,例子中的匿名内部类太简单了,你才会有兴趣看checkYears方法的参数,才能看到这个checkYears方法,才会提前知道这段代码要干什么。如果这个匿名内部类很复杂,你肯定不会第一时间关注参数,而会去寻找check方法的具体实现。

继续上文,找到check方法之后,就会看到了调用checkYears方法。

checkYears方法在哪里?在Arithmetic对象中。Arithmetic对象是什么?是一个匿名内部类。

之后我们回头去看匿名内部类的实现,发现Arithmetic匿名内部类实现了一个checkYears方法。这个checkYears方法干了什么?对传入的interviewee对象的属性进行校验,返回true/false。

也就是说,我们必须从头至尾地review,直到看完了匿名类中的方法,才知道这段代码要干什么。

现在我们使用lambda代替匿名内部类:

重新review一次:

首先从main方法开始,看到了调用check方法。

看到check方法的时候,我们马上会注意到有一条lambda表达式。这时候,我们马上意识到“这条lambda表达式很重要,是check方法中的一个重要步骤”,从而把注意力集中到lambda表达式上。

通过看这条lambda表达式,我们马上就意识到:

check方法中有个这样的算法:参数是Interviewee对象,对Interviewee对象中的age和workExperienceYears进行了校验,返回了true/false。

只是看到check方法而已,我们就已经知道这段代码的意图是什么。

很明显,使用lambda表达式,可以使代码的可读性更好,表达更清晰。这也是函数式编程的其中一个目的。

之所以很多人觉得lambda难学,是因为lambda的风格和“传统”的Java代码迥异,一下子转不过弯过来。其实在稍微熟悉lambda之后,会发现代码很好理解,思路非常清晰。

关于“Java 8的新特性lambda表达式是否比匿名内部类具有更好的可读性?”的讨论,可以参看:https://www.zhihu.com/question/28548584

我觉得在此回答中,表述得已经非常清楚。

二、一些实现

(1)遍历list

test.java

foreach方法我们肯定是司空见惯了。我个人认为写成传统的foreach循环已经非常好理解,但是我们可以尝试理解一下:

对list对象进行foreach遍历。对遍历的每个元素x进行syso操作。

我个人认为,在写lambda表达式的时候,一定要有这样的思路:

1.对象是什么。这里的对象是list遍历得到的元素,我们定义一个x来指代。

2.我们如何操作这个对象?我们要输出对象,所以使用“对象”->“操作”的方式进行输出。

当然这个例子很简单,非常好写。如果lambda涉及的对象很多,往往就不那么好写了,需要使用一些套路。

(2)写lambda的套路

拿上面的例子,我们要把整个匿名类转成lambda表达式,怎么转换?

首先我们去掉匿名类的包装、方法名和方法返回类型,保留方法参数和方法体,并且在参数和方法体之间用“->”连接:

然后去掉花括号、return和分号:

如果只有一个对象(这里只有Interviewee一个对象),那么可以把括号去掉(其实不去掉也没关系,但是如果有多个对象,那么不能去掉括号),把interviewee改为i,就得到了:

(3)lambda表达式中的map方法

注意这里不是Java中的Map,而是lambda表达式中的map方法。

map的作用是将一个对象变为另外一个。

test.java

在这里,stream方法表示对list集合进行操作,map方法对集合中的每个元素进行了x + x * 0.05的运算,之后进行了替换。最后使用forEach方法遍历list中的元素,逐个输出。

(4)lambda表达式中的reduce方法

reduce的作用是将所有值合并为一个。

test.java

我们可以这样理解:

map对集合中的所有元素进行处理后,进行reduce操作。reduce需要计算出总值,所以需要有一个sum对象,而每个元素就是x。reduce遍历整个list,将list中的元素x不停累加起来,最后就得到了总值,通过get方法进行获取。

如果还是不好理解,可以尝试用传统方法实现这个效果,然后用套路进行转换。

(5)lambda表达式中的filter方法

filter方法可以过滤元素。

test.java

三、性能问题

经过测试,我认为lambda表达式比传统方法更慢。

Test.java

运行结果:

其实也没有慢到哪里去,还算在可接受范围内。

顺便提一下:虽然lambda表达式看着很高端,但是其本质只是“语法糖”而已。

在编译时,编译器会把lambda转换成正常的代码(但是要花更多的时间),所以你可以用更少更精练的代码实现同样的效果,同时让你的代码更好理解。

但是这个“更好理解”是相对的,如果别的程序员没有掌握这种知识,看到你的代码肯定会头痛。这时你就应该权衡一下是否应该使用lambda表达式。

四、总结

通过学习lambda,我们更应该领悟到函数式编程的思想。

下次写一篇文章来讨论函数式编程。

发表评论

电子邮件地址不会被公开。 必填项已用*标注