JDK 8 的 Lambda 表达式 -- 应用背景

即将到来的 JDK8 最为引人入胜之处非 Lambda 表达式莫数了,这在其他动态语方,如 Ruby, Groovy, Scala 等语言中早已大行其道。一旦 JDK 搭上了 Lambda 这趟车,从此操作事件,线程,处理集合时又大为方便了。关于现阶段如何体验 JDK8 的特性可以参考 抢鲜设置 JDK8 的编程环境,印象

本文主要参考于官方的 State of the Lambda,并对源码或反编译出字节码,作一定的深入以助于各位理解,在 JVM 中是如何实现 Lambda 的。

Lambda 表达式,又称闭包(Closure),或称匿名方法(anonymous method)。这在其他语言中,如 Ruby, Groovy, Scala, JavaScript 等,甚至是在 C# 中运用得如火纯清的特性,JDK8 这才问候他,真有些晚了。没有 Lambda 时,Java 不得不求助于匿名类的回调方法来达到相似的目的,为了捕获外部变量,变量必须声明为 final。

一见到 Lambda,第一个反应就是 Lambda 表达式(局部而已),其实我们这里要说的 JDK8 的 Lambda 包含以下几块内容:

  1. Lambda 表达式,俗称闭包或匿名方法
  2. 方法和构造器引用
  3. 扩充的目标类型和更强的类型推断
  4. 接口中的默认静态方法

Lambda 这一特性使得 Java 也开始向函数式编程倾斜,关键是能更有效的应对并发环境。

说到 Lambda,我们不得不了解一下当前背景,例如下面一个典型的事件处理场景:

这有什么不好呢,这里有不好的理由 ,主要如下:

  1. 代码冗长,有更简单的谁想这么写
  2. this 的指向在变,这与 JavaScript 中的 this 一样烦人
  3. 不灵活的类加载和实例创建语义,就是因为 new ActionListener() 时创建了一个  ActionListener 的子类实例
  4. 不能捕获非 final 的本地变量
  5. 不能在控制流上进行抽象

如果使用上 Lambda  的话,上面的代码可以改写得很优雅简洁,我们不急于此,先来介绍一个关键性的概念,功能性接口

何谓功能性接口,像 ActionListener 那样的,再如 Runnable 和 Comparator 只有一个抽象方法的接口我们就称之为功能性接口,又简称  SAM 类型,即  Simple Abstract Method。

为什么 Lambda 就认准了这种 SAM 类型呢,因为仅一个抽象方法时,对于编译器只有一条路可以走,才得了进行精确的参数与返回类型的推断,这是我的理解。

不需要特别去声明是否是一个功能性接口,编译器看到是只有一个方法的接口就认为是功能性接口,像接口里若是声明了象是来自于 Object 类的 toString() 方法,或是有静态的或默认的方法也不会影响到编译器的判断。API 设计时常用 @FunctionalInterface 注解去标识一个功能性接口,编译器也会据此检验是否符合功能性接口的约束。

@FunctionalInterface 只是一个标识给编译器用的注解,如果标注在一个非功能性接口上编译器便会报错 

Invalid '@FunctionalInterface' annotation; Xxx is not a functional 

功能性接口意味着它可以用于创建 Lambda 表达式,方法引用和构造器引用。

JDK8 在实现 Lambda 过程中曾试图引用一种新的结构化函数类型,即箭头类型,比如定义从 (String, Object) -> int 的函数,但至少目前为止是否决了这一方案。尚不知这种结构化的函数类型是怎么样个写法,这得看关于 Lambda 的初稿,大约不用管这些了。

说是因为结构化的函数有几个缺点:

  1. 会使得类型系统更复杂
  2. 会导致多种风格的库并存 -- 某些库仍然用回调接口,有些使用新的函数类型
  3. 这种语法形式难以驾驭,特别是涉及到检测性异常时
  4. It is unlikely that there would be a runtime representation for each distinct function type, meaning developers would be further exposed to and limited by erasure. For example, it would not be possible (perhaps surprisingly) to overload methods m(T->U) and m(X->Y) -- 尚不知这条讲的是什么意思

因为其实我们有很多库是符合功能性能接口需求的,所以只要在 JDK8 中作出一个约定,那么那些现成的功能性接口就能用于 Lambda 表达式中。比如 JDK7 里的这些接口

在它们都是 SAM,都能加入到 Lambda 的事业中去。除此之外 JDK8 添加了一个新包 java.util.function,里面有一些通用的功能性接口:

  • Predicate<T> -- 为对象评估出真与假
  • Consumer<T> -- 接收对象执行一个操作t
  • Function<T,R> -- 把 T 类型转换为 R 类型的函数
  • Supplier<T> -- 产生一个 T 类型的实例 (像是一个工厂)
  • UnaryOperator<T> -- 从 T 类型 到 T 类型的一元操作
  • BinaryOperator<T> -- 从  (T, T) 到 T 的二元操作

另还有更具体的功能性接口,如 IntSupplier, LongBinaryOperator 等,或者是更多元的函数,如 BiFunction<T, U, R> 表示的是从  (T, U) 到 R 的函数。

小结:

本篇至此还未让你看到 Lambda 表达式的影子,主要是讲什么是功能性接口,即 SAM,只有一个抽象方法的接口,这样的接口才能被应用到 Lambda 中去。JDK8 中有了功能性接口这么一个约定,可以保持很好的与 JDK8 之前版本的类库兼容,这也是我当听说 JDK8 要上 Lambda 时所思考过的问题。

下面的篇章要真正去领略 Lambda 了。

本文链接 https://yanbin.blog/jdk-8-lambda-1-background/, 来自 隔叶黄莺 Yanbin Blog

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

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments