1关于实践
这里依旧我们通过学生筛选的例子,看看在实践中如何通过Lambda表达式使得代码更加灵活、简洁。现在我们需要从全体学生中,将英语成绩在95分以上的学生筛选出来。筛选代码如下:
现在的代码的确满足了我们的需求,但如果我们需要将筛选分数改成80分,并且95分的筛选方法以后还有可能需要用时该怎么办?如果在实际开发中,你可能也不会想使用策略模式(尽管还是很啰嗦),更直接的是直接写出来studentFilterByEnglish95()和studentFilterByEnglish80()两个函数来满足需求了。那现在我需要过滤75分的学生,需要过滤数学成绩,需要过滤科学成绩呢?是否会有一堆啰里啰嗦的过滤函数被写出来,看过上一节的同学一定明白,此时最好的处理思路是传递学生的判断代码,即行为参数化,重用过滤执行机制。
Step1 : 行为参数化
传递行为正是Lambda表达式的独门秘诀,我们希望结合Lambda表达式,新的学生过滤函数使用方式应该如下:
Step2 :函数式接口声明
在上一篇文章中我们说过,Lambda的使用环境上下文必须是函数式接口,很显然学生判断过滤代码只有if判断条件需要根据条件更改,所以我们现在需要一个能与 Student -> boolean匹配的函数式接口的声明,并将该接口作为studentFilter的参数,便可以实现行为的传递了,代码如下:
现在我们就可以将StudentFilter这一函数式接口作为学生过滤函数的参数,传递给它了:
Step3 :行为执行
结合StudentFilter接口,改用StudentFilter.test()用于学生的判断:
Step4 : 传递Lambda
此时,我们便可以使用studentFilter方法来传递Lambda表达式了,正如Step1中我们所期望的使用方式那样使用,只需要注意Lambda的函数签名必须与函数式接口声明的抽象方法对应。
2关于Lambda表达式的类型推断
在Java 7中,已经引入了 菱形运算符(<>),Java可以利用泛型从上下文中推断出合适的类型参数的特性。利用该特性,Java编译器同样可以在Lambda表达式中,结合函数式接口的声明,便可以推断出参数列表的参数类型,从而可以写出更加简洁的Lambda表达式,如下:
可以看到Java对于Lambda表达式支持类型推断,这些年Java的确从C#的语法特性中汲取了很多好用的特性。
3
3Lambda的变量访问
在我们目前遇到的所有示例中的Lambda主体中,都只是用了参数列表中的变量,其实Lambda是支持引用外部变量的。但是对于外部变量的引用Lambda是有限制的,如果外部变量是实例变量或静态变量可以直接使用,但如果是局部变量,那局部变量必须是final或effectively final才可以引用,如下就无法通过java编译:
为什么会有这样的限制?这要通过JMM来考虑,在JMM中局部变量与实例变量在Java内存中存储在不同的内存区域,实例变量存储在Java堆中,而局部变量存储在线程栈中。如果Lambda直接访问局部变量,而且Lambda是在一个线程中使用,则使用Lambda的线程可能会在局部变量已经被释放的情况下再去访问。而在JMM模型中,线程对于实例变量的操作都是副本操作,然后通过副本与主存之间的刷新进行同步的。final域的添加,确保了该局部变量只会有一个不变值,从而在多线程情况下的线程安全性,为什么String类型是线程安全的?就是因为它只会存在一个值。
在使用Lambda表达式之前,一定要明白自己实现的是一个什么样的函数接口,本文我们根据函数式接口的语法规则定义了StudentFilter函数式接口,从而实现了可以在filter方法中传递行为。但是在开发中,我们常常会用到类似签名接口,例如 () -> boolean, (T) -> void, ( T ) -> ( T )等等,Java 8将一些通用形式的接口进行了定义,我们将在下节来讨论这些JDK中的函数式接口。