JDK8 的 Lambda 表达式 -- 方法引用

Lambda 允许我们定义匿名方法(即那个 Lambda 表达式,或叫闭包),作为一个功能性接口的实例。如果你不想把一个 Lambda 表达式写得过大,那么你可以把表达式的内容分离出来写在一个方法中,然后在放置 Lambda 表达式的位置上填上对那个方法的引用。

方法引用也应看作是一个 Lambda 表达式,所以它也需要一个明确的目标类型充当功能性接口的实例。简单说就是被引用的方法要与功能接口的 SAM(Single Abstract Method) 参数、返回类型相匹配。方法引用的引入避免了 Lambda 写复杂了可读性的问题,也使得逻辑更清晰。

为了应对方法引用这一概念, JDK8 又重新借用了 C++ 的那个 “::” 域操作符,全称为作用域解析操作符。

上面的表述也许不好明白,我看官方的那份 State of the Lambda 也觉得不怎么容易理解,特别是它举了那个例子很难让人望文生意。我用个自己写的例子来说明一下吧。

目前的 Eclipse-JDK8 版还不能支持方法引用的特性,幸好就是在昨天正式版的 NetBeans IDE 7.4 对 JDK8 有了较好的支持,所以在 NetBeans 7.4 中写测试代码。

说明:

1. Machine 是一个功能性接口,它只有一个抽象方法
2. start(Machine machine) 方法为 Lambda 表达式提供了一个上下文,表明它期盼接收一个 Machine 的功能性接口类型
3. start((id, task) -> id + ": " + task), 是传递了一个 Lambda 表达式给 start() 方法
4. start(TestJdk8::hello) 是把指向 TestJdk8::hello 方法的引用传递给了 start() 方法,这里可以理解 hello() 方法是 Lambda 表达式的另一种表现形式。

对应一下两个 start() 方法调用的参数,Lambda 表达式的参数列表 (id, task) 与 hello 方法的参数 (int id, String task) 是一致的,返回值类型也是一致的。

想像一下如果一个 Lambda 表达式的代码量很大,全部挤在一起作为 start() 方法的参数部分,混乱也不太方便于单步调试。所以可以把 Lambda 的实现挪出来放在一个单独的方法中,在使用处只放置一个对该方法的引用即可。借助于方法引用,JDK8  把方法与 Lambda 表达式巧妙的结合了起来,直接的说 Lambda 表达就是一个方法,它用自己的方法列表和返回值。

那么符合什么条件的方法可以作为 Lambda 表达式来用呢?答:方法签名与功能性接口的 SAM 一致即可。比如,可以进行下面的赋值:

有些什么样子的方法引用:

  1. 静态方法 (ClassName::methName)
  2. 对象的实例方法 (instanceRef::methName)
  3. 对象的super 方法 (super::methName)
  4. 类型的实例方法 (ClassName::methName, 引用时和静态方法是一样的,但这里的 methName 是个实例方法)
  5. 类的构造方法 (ClassName::new)
  6. 数组的构造方法 (TypeName[]::new)

第 1 条,静态方法以 ClassName 为作用域好理解,第 4 条中实例方法也可以用 ClassName::methName 的方式去引用,那么这里又有个约定了:如果实例方法用类型来引用的时候,那么调用时第一个参数将作为该引用方法的接收者,其余参数依次作为引用方法的参数。举个例子:

上面的代码应该能有助于理解实例方法用类型来引用,如果引用的是泛型方法,类型写在 :: 之前。

同样当然对于第 2 条,引用实例方法时,SAM 的第一个参数也作为接收者,其作参数依次填充过去。

第 5 条,类的构造方法要用类型去引用,new 相当一个返回当前类型实例的实例方法,所以

数组是种类型,可以认为数组的构造方法是只接受一个整形参数,所以能这样引用数组的构造方法:

小结:Lambda 表达式就是一个功能性接口的实例,因而调用方式参照功能性接口。Lambda 表达式可抽取到一个方法中,然后用方法引用指向这个方法,被引用的方法签名与功能性接口的 SAM 的一致性,注意引用实例方法时,SAM 的第一个参数将作为引用方法的接收者。我们把数组理解为有一个接收整数的构造方法。

类别: Java/JEE. 标签: , . 阅读(556). 订阅评论. TrackBack.

Leave a Reply

Be the First to Comment!

avatar