接上一篇:初探Lambda表达式/Java多核编程【2】并行与组合行为

本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践。

本章将介绍Lambda表达式基础知识。

前言

把上一张书中的结语放到这里作为本章学习内容的开头,以此来概括Lambda表达式的优点:

  • 提升性能、自动的并行化
  • 更棒的API(comparing(...)细粒度的方法将成为标准)
  • 编码风格得到改进、代码简化

反观前面几篇文章中的代码实践,以上三个优点全部得到了验证。

Lambda语法

前文中我们已经提到,Java中无法声明独立的纯函数,但是Lambda的出现提供了一种与独立函数更为近似的实现方式。就只看Lambda形式,的确与很多精简语法的脚本语言中所声明的函数高度相似:

# CoffeeScript
eat = (x) ->
    alert("#{x} has been eatten!")

总之光看上去就像那么回事:)

那么Lambda表达式的语法又是怎样的呢?

  • 参数列表
  • 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,也可以添加注解(@Nullable, etc.)。

表达式体部分可以为方法的调用,如str.length()等等,也可以是表达式,如加减乘除等等,即“语句Lambda”与“表达式Lambda”这两种形式。

另外关于返回值,有则用return sth_to_return;,没有则用return;或直接不写返回语句。

最后,需要注意的是Lambda表达式不需要也不允许使用throws关键字来声明可能产生并需要向上抛出的异常。

Lambda与匿名内部类

前几篇文章中常常将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();

注意此时需要打印的内容也同时自动变成了LetsRun.aString,印证了上述特征,即Lambda不能直接访问父类型中的名字。

关于对外部变量的访问(后面书中将此称为“变量捕获”),不论是匿名内部类还是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的值,编译就无法通过。

同样,折叠为Lambda后,依然是合法的:

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相当于语句块,因此表达式体内持有和外部相同的语境,即thissuper拥有相同含义:

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中的thissuper的含义完全同外部一致。

而若在匿名内部类访问外部对象的当前实例须用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

初探Lambda表达式/Java多核编程【3】Lambda语法与作用域的更多相关文章

  1. 初探Lambda表达式/Java多核编程【1】从集合到流

    从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...

  2. 初探Lambda表达式/Java多核编程【2】并行与组合行为

    今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...

  3. 初探Lambda表达式/Java多核编程【4】Lambda变量捕获

    这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来.加上假期两个月左右都过去了书才看了1/7都 ...

  4. 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代

    开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...

  5. Java 函数式编程(Lambda表达式)与Stream API

    1 函数式编程 函数式编程(Functional Programming)是编程范式的一种.最常见的编程范式是命令式编程(Impera Programming),比如面向过程.面向对象编程都属于命令式 ...

  6. Java 函数式编程和Lambda表达式

    1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...

  7. Lambda&Java多核编程-5-函数式接口与function包

    从前面的总结中我们知道Lambda的使用场景是实现一个函数式接口,那么本篇就将阐述一下何为函数式接口以及Java的function包中提供的几种函数原型. 函数式接口 早期也叫作SAM(Single ...

  8. Lambda&Java多核编程-6-方法与构造器引用

    在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. ...

  9. Java函数式编程和lambda表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...

随机推荐

  1. leetcode-005 reorder list

    1 package leetcode; public class ReOrderList { public void reorderList(ListNode head) { if(head==nul ...

  2. ASP.NET脚本过滤-防止跨站脚本攻击[转]

    ASP.Net 1.1后引入了对提交表单自动检查是否存在XSS(跨站脚本攻击)的能力.当用户试图用<xxxx>之类的输入影响页面返回结果的时候,ASP.Net的引擎会引发一个HttpReq ...

  3. 【NOIP2015】反思+题解

    D1T1> 神奇的幻方 模拟即可. #include <cstdio> #include <cstring> #include <algorithm> #de ...

  4. GP项目总结(一)

    1.使用activity渲染不同的View时,两种方法: (1.)自定义两个不同的View,然后在mainActivity里根据不同的数据使用不同的View,通过addView()来Activity里 ...

  5. Emmet插件详解

    http://www.ithao123.cn/content-10512551.html   (webstorm的css编写插件)Emmet:HTML/CSS代码快速编写神器 [摘要:Emmet的前身 ...

  6. webpack之代码分离

    https://robertknight.github.io/posts/webpack-dll-plugins/ webpack一般会把一个文件里import/require的文件都会打包在一起,最 ...

  7. Java Swing JScrollPane 设置滚动量

    JScrollPane.getVerticalScrollBar().setUnitIncrement(20); 参考:http://bbs.csdn.net/topics/320249228

  8. 第一部分 DotNET与C#概述

    DOTNET是.NET Framework框架分为公共语言运行时(CLR)和框架类库(FCL). C#是C#(CSharp)语言(编程语言). 模式: 硬件--->操作系统OS分为非托管应用程序 ...

  9. PHP + Memcache 实现Session共享

    一.安装Memcache和PHP扩展 Windows下的Memcache安装:1. 下载memcache的windows稳定版,解压放某个盘下面,比如在c:\memcached2. 在终端(也即cmd ...

  10. Flex 开发框架汇总

    1.现有成熟Flex框架   Cairngorm (Adobe Open Source) - MVC framework   PureMVC (Open Source) - MVC framework ...