接上一篇:
本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践。
本章将介绍 Lambda 表达式基础知识。
把上一张书中的结语放到这里作为本章学习内容的开头,以此来概括 Lambda 表达式的优点:
细粒度的方法将成为标准)
- comparing(...)
反观前面几篇文章中的代码实践,以上三个优点全部得到了验证。
前文中我们已经提到,Java 中无法声明独立的纯函数,但是 Lambda 的出现提供了一种与独立函数更为近似的实现方式。就只看 Lambda 形式,的确与很多精简语法的脚本语言中所声明的函数高度相似:
- # CoffeeScript
- eat = (x) ->
- alert("#{x} has been eatten!")
总之光看上去就像那么回事:)
那么 Lambda 表达式的语法又是怎样的呢?
两部分之间使用
分割,看几个例子:
- ->
- p -> p.translate();
- i -> new Point();
- (a, b) -> return a + b;
- () -> "Ha!";
- (x, y, z) -> {
- x += y;
- y += z;
- z += x;
- }
箭头左边接收任意数量的参数,右边则为表达式体,描述所需的行为。
显而易见,在一般情况下无需显式地指定参数类型,除非上下文的信息无法是编译器推断出相应的类型:
- (int x, int y) -> x + y;
参数可以声明为
,也可以添加注解(
- final
, etc.)。 表达式体部分可以为方法的调用,如
- @Nullable
等等,也可以是表达式,如加减乘除等等,即 "语句 Lambda" 与 "表达式 Lambda" 这两种形式。 另外关于返回值,有则用
- str.length()
,没有则用
- return sth_to_return;
或直接不写返回语句。 最后,需要注意的是 Lambda 表达式不需要也不允许使用
- return;
关键字来声明可能产生并需要向上抛出的异常。
- throws
前几篇文章中常常将 Lambda 与匿名内部类做粗浅的类比与对比,现在我们将就这一点做具体深入的分析。
首先在语法层面,Lambda 表达式有时候被称为匿名内部类的 "语法糖",这表明了二者之间存在语法繁简的明显区别。
其次便是标识性问题,我们知道 Java 中为了区分对象,每一个对象(即使是匿名内部类的实例)都具有唯一标识,而依赖于对象而存在的行为(即我们所说的方法)也会与此标识相关联。
例如:
- String bar = "bar";
- String foo = "foo";
- System.out.println(bar.hashCode()); // => 97299
- System.out.println(foo.hashCode()); // => 101574
但是对于 Lambda 表达式而言,情况便不是如此的明朗,根据具体情况的不同,Lambda 自身可能拥有标识也可能没有。
况且,Lambda 为的就是表示一种行为,趋向于纯函数,因此一般情况下是不需要使用标识加以区分的。
再者就是两者的作用域大小的区别。
对于匿名内部类而言,显而易见,在类内可以沿用父类型(即函数接口)的名字。
而对于 Lambda,则不能。
我们用 Runnable 接口来举一个例子:
- public interface LetsRun extends Runnable {
- String aString = "Big brother is watching.";
- }
- new Thread(
- new LetsRun() {
- @Override
- public void run() {
- System.out.println(aString);
- }
- }).run();
显然,匿名内部类能够直接沿用我们在 LetsRun 这个函数式接口中声明的 aString。
写完这段代码的同时,IDE 给了我一个可以将匿名内部类折叠为 Lambda 的提示,现在就让它帮我们自动折叠一下:
- new Thread((LetsRun) () -> System.out.println(LetsRun.aString)).run();
注意此时需要打印的内容也同时自动变成了
,印证了上述特征,即 Lambda 不能直接访问父类型中的名字。
- LetsRun.aString
关于对外部变量的访问(后面书中将此称为 "变量捕获"),不论是匿名内部类还是 Lambda,对于域外部变量的权限都是有限的。
在匿名内部类中,可以读取外部量,但是不允许有修改变量的倾向。
也就是说,没有严格的限制规定被访问的外部量必须被声明为
:
- final
- // This is OK
- String anotherString = "WAR IS PEACE / FREEDOM IS SLAVERY / IGNORANCE IS STRENGTH";
- // Also OK
- final String finalString = "Nineteen Eighty-Four";
- new Thread(new LetsRun() {
- @Override
- public void run() {
- System.out.println(aString + "\n" + anotherString + "\n" + finalString);
- }
- }).run();
倘若一旦在方法内修改了
的值,编译就无法通过。
- anotherString
- new Thread((LetsRun) ()
- -> System.out.println(LetsRun.aString + "\n" + anotherString + "\n" + finalString)).run();
关于变量捕获的问题是下一小节的重点内容,在此暂时不做深究。
Lambda 表达式在定义时,参数部分与表达式体内的命名可以暂时屏蔽掉字段的名称:
- public class Foo {
- String x,
- y;
- BinaryOperator binaryOperator = (x, y) - >x.hashCode() + y.hashCode();
- // ...
- }
另外,Lambda 相当于语句块,因此表达式体内持有和外部相同的语境,即
与
- this
拥有相同含义:
- super
- public class MySuperClass {
- public static final String aString = "Father";
- }
- public class MyClass extends MySuperClass {
- public static final String aString = "Son";
- public void aMethod() {
- new Thread((LetsRun) () -> {
- System.out.println("--- Lambda ---");
- System.out.println(super.aString);
- System.out.println(this.aString);
- }).run();
- System.out.println("--- Outside ---");
- System.out.println(super.aString);
- System.out.println(this.aString);
- }
- }
运行结果:
- --- Lambda ---
- Father
- Son
- --- Outside ---
- Father
- Son
Lambda 无法引用自身,因此可以用一种尴尬的方式递归调用自己:
- intUnaryOperator = i -> i == 0 ? 1 : i * intUnaryOperator.applyAsInt(i - 1);
Lambda 不从父类型中继承任何名字,包括:
字段
- final
将全部被排除在作用域之外。
Lambda 参数与表达式体中的局部声明可以屏蔽字段名。
Lambda 中的
和
- this
的含义完全同外部一致。 而若在匿名内部类访问外部对象的当前实例须用
- super
,非常笨拙:
- OuterClass.this
- new Thread((LetsRun) () ->
- System.out.println(Foo.this.getClass().toString())
- ).run();
递归 Lambda 时须注意 Lambda 变量无法被初始化,只能直接调用相应函数式接口中的方法。
本章代码:
Foo.java
- import java.util.function.BinaryOperator;
- public class Foo {
- String x, y;
- BinaryOperator binaryOperator = (x, y) -> x.hashCode() + y.hashCode();
- public static void main(String[] args) {
- String bar = "bar";
- String foo = "foo";
- System.out.println(bar.hashCode());
- System.out.println(foo.hashCode());
- new Thread((LetsRun) () -> System.out.println(LetsRun.aString)).run();
- String anotherString = "WAR IS PEACE / FREEDOM IS SLAVERY / IGNORANCE IS STRENGTH";
- final String finalString = "Nineteen Eighty-Four";
- new Thread((LetsRun) () -> System.out.println(LetsRun.aString + "\n" + anotherString + "\n" + finalString)).run();
- new MyClass().aMethod();
- new Foo().accessOuterClassInAnnoymousInnerClass();
- }
- public void accessOuterClassInAnnoymousInnerClass() {
- new Thread((LetsRun) () ->
- System.out.println(Foo.this.getClass().toString())
- ).run();
- }
- }
LetsRun.java
- public interface LetsRun extends Runnable {
- String aString = "Big brother is watching.";
- }
MyClass.java
- import java.util.
- function.IntUnaryOperator;
- public class MyClass extends MySuperClass {
- public static final String aString = "Son";
- IntUnaryOperator intUnaryOperator = null;
- public void aMethod() {
- new Thread((LetsRun)() - >{
- System.out.println("--- Lambda ---");
- System.out.println(super.aString);
- System.out.println(this.aString);
- }).run();
- System.out.println("--- Outside ---");
- System.out.println(super.aString);
- System.out.println(this.aString);
- }
- public void factorial() {
- intUnaryOperator = i - >i == 0 ? 1 : i * intUnaryOperator.applyAsInt(i - 1);
- }
- }
MySuperClass.java
- public class MySuperClass {
- public static final String aString = "Father";
- }
以及运行结果:
- 97299
- 101574
- Big brother is watching.
- Big brother is watching.
- WAR IS PEACE / FREEDOM IS SLAVERY / IGNORANCE IS STRENGTH
- Nineteen Eighty-Four
- --- Lambda ---
- Father
- Son
- --- Outside ---
- Father
- Son
- class Foo
来源: http://www.cnblogs.com/hwding/p/6432084.html