Java 8 Lambda 捕获外部变量

可能会把捕获外部变量的 Lambda 表达式称为闭包,那么 Java 8 的 Lambda 可以捕获什么变量呢?

  1. 捕获实例或静态变量是没有限制的(可认为是通过 final 类型的局部变量 this 来引用前两者)
  2. 捕获的局部变量必须显式的声明为 final 或实际效果的的 final 类型

回顾一下我们在 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为  final,例如下面的代码放在 Java 7 中是编译不过的

java7-annymous-capture-local-variable

Java 7 要求 version 这个局部变量必须是 final  类型的,否则在匿名类中不可引用。 阅读全文 >>

Java 8 Lambda 写法与简化

Java 8 的 Lambda 表达式的实现方式还是基于已有的字节码指令,由 Lambda 表达式的方法签名结合上下文并经由 SAM 推断出正确的类型来。Java 8 的 Lambda 完整书写格式是

(type parameter1 [type parameter2, ...type parametern]) -> { statements here }

这种标准格式所表示的就是方法签名。

虽不及其他语言的 Lambda 表达式,像 Swift, Scala 可省略参数部分,可用默认参数名 $0, $1, 或 _, 但 Java 8 的 Lambda 还是可以进行酌情简化

  1. 参数类型总是可省略   --     (x, y) -> { x + y; }
  2. 参数为一个时,参数括号可省略  --    x -> { System.out.println(x); }
  3. 语句为一条时,可省略大括号, 并且语句后不要分号 --  x -> System.out.println(x)
  4. 上面更进一步,如果是单条 return 语句,必须把 return 关键字去掉  --  x -> "Hello " + x
  5. 就差一点,参数部分总是不能省,无参必须写成 () -> System.out.println("hi")
  6. Java 8 中若要近似的实现无参数部分写法,那就是方法引用了 -- System.out::println

阅读全文 >>

JDK8 的 Lambda 表达式 -- 理解新式集合操作

有了前面的 SAM,Lambda 表达式,以及默认接口方法作铺垫后,我们可以去很好的去理解 Java8 用 Lambda  表达式操作集合的基本原理了。此篇我们想要化解的示例代码如下:

如果对其他支持闭包的语言,如 JavaScript, Groovy, Ruby, Scala 等有所了解的话,很容易看出前面的代码输出为

abc
cde 阅读全文 >>

JDK8 的 Lambda 表达式 -- 默认和静态接口方法

进入 Java8 之后我们会发现接口可以有方法实现了,这与我们一直看待 Java 接口的观念产生了冲突,不过也别急,接口中的方法实现必须是一个默认方法,即像

interface Shape {
    default boolean isShape() {
        return true;
    }
}

本文旨在探讨 Java8 的默认接口方法存在的合理性,Java8 在这点上如何保持与前面版本的兼容性。

Lambda 和方法引用使得 Java 语言更具表现力。说到 Lambda 和方法引用的关系,Lambda 表达式的目的就是让你更为便捷的去绕过对象直接引用方法。

接口应该是相对稳固的,我们应该有这样的经验,类中使用了接口中定义的常量,如果在接口中改变了该常量值,单纯的替换接口对应的 class 文件是不奏效的,因为编译类时其实是把接口中的常量直接固化在类中了。如果类中要体现出最新常量值,那么使用接口的类也要重新编译。即使在接口中添加或改变了方法定义,也不能强制使用到它的类重新编译,早先的类完全可以自由的运行,因为接口中定义的常量和方法的所有内容都在自身,类一旦编译后便可脱离所实现的接口而运转。 阅读全文 >>

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 中写测试代码。 阅读全文 >>

JDK8 的 Lambda 表达式 -- 词法范围和变量捕获

Lambda 表达式的词法范围,一言以蔽之就是没有引入新的词法范围。这里的词法范围要研究的课题是 this 的指向有没有在变。我们知道在匿名类内部 this 指向的匿名类的实例,这种关系是在编译期就确定的。而在 Lambda 表达式中的 this 与外部的 this 没有差别,也就是说你可以把 Lambda 表达式当成一般的语句来看待,多简单啊,不像 JavaScript 中的 this 被搞的那么魔幻。

可以这么测试:

输出的是同一个地址里的东西:

cc.unmi.testjdk8.TestLambda@28a418fc
cc.unmi.testjdk8.TestLambda@28a418fc

接下来瞧瞧 Lambda 表达式对外层变量的捕获。Lambda 表达式的有个好处就是它是轻量级,可重用单元,并且可捕获外层变量。 阅读全文 >>

JDK8 的 Lambda 表达式 -- 类型推断

现在我们来看看 JDK8 是怎么对 Lambda 表达式进行类型推断的,Lambda 的实际类型叫做它的目标类型(target type),因为 JDK8 沿承了已有的类型系统,所以象这样的写法:

button.addActionListener((ActionEvent e) -> foo());

addActionListener() 方法接收的是一个 ActionListener 类型的参数,所以这里的 Lambda 表达式 (ActionEvent e) -> foo() 代表的就是一个 ActionListener 实例,编译器是怎么知道这一点的,而且相同的 Lambda 是可以表示不同的类型的,见:

Callable<String> c = () -> "done";     //这个 () -> "done" 是 Callable<String> 类型
PrivilegedAction<String> a = () -> "done";  //同样的 () -> "done" 却是 PrivilegedAction<String> 类型

答曰,根据 Lambda 表达式所处的上下文去感知。上下文决定了 Lambda 所期盼的类型,比如说变量声明类型,方法要求的输入参数类型,所以它必须是明确,不能模棱两可。所以一个 Lambda 表达式能否放在某处需要满足以下几个条件:

  1. 期盼的类型必须是一个功能性接口,这样就能唯一定位到那个抽象方法上去,确定方法签名,接下来就是 阅读全文 >>

JDK8 的 Lambda 表达式 -- 实现原理初探

JDK8 使用一行 Lambda 表达式可以代替先前用匿名类五六行代码所做的事情,那么它是怎么实现的呢?从所周知,匿名类会在编译的时候生成与宿主类带上 $1, $2 的类文件,如写在 TestLambda 中的匿名类产生成类文件是 TestLambda$1.class, TestLambda$2.class 等。

我试验了一下,如果使用的是 Lambda 表达式并不会生成额外的类文件,那么字节码里是什么样子的?来看下用  javap -c 反编译出下面文件产生的 TestLambda.class,两个方法,一个是  byAnonymousClass() 使用匿名类,另一个是 byLambda 使用 Lambda 的方式:

阅读全文 >>

JDK 8 的 Lambda 表达式 -- 写法

过去,我们使用匿名类的实现像  ActionListener 这样的接口,即使最简单的情景都需要写上五六行代码,这就显得有些累赘了。因为其实我们在 new ActionListener {} 时就是在创建一个 ActionListener 子类型,重载了方法,并初始化一个实例出来。现在我们手上有了 Lambda 表达式这一武器的话,就变得简单明了了,所以说  Lambda 是一种轻量级的实现机制。

这里是几个 Lambda 表达式的样子:

Lambda 的格式就是三部分:参数列表,-> 分隔符,执行体。其他语方的 Lambda 格式都这样的,可能就是分隔符不一样,如 Ruby 的 |,Scala 的 => 等。

执行体里的 return 只是标明返回到匿名方法的调用者,不是返回值的意思; 最上层是不能用 break 和 continue 关键字的,可用在循环中; 如果 Lambda 表达式有返回值,那么在每一条路径上都要有返回值或是抛出异常。路径上最后一条语句的返回值即为 Lambda 表达的返回值。 阅读全文 >>

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,我们不得不了解一下当前背景,例如下面一个典型的事件处理场景: 阅读全文 >>