1.1 Why Lambdas?

当你操作多线程的时候,你会像下面这样将要处理的代码放到run()函数中:

class Worker implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++)
doWork();
}
...
}

然后,当你想要执行这段代码的时候,需要构造Worker的实例来执行它。你可以把放入到线程池或者简单处理启动一个线程:

Worker w = new Worker();
new Thread(w).start();

这段代码的重点在于你要将你想要处理的逻辑放入到run()方法中。

再来考虑一个场景,自定义排序。如果你不想按照字典的默认排序,而是想要按照字符串的长度来进行排序的话,你需要通过一个Comparator对象来进行排序:

class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return Integer.Compare(first.length(), second.length());
}
} Arrays.sort(strings, new LengthComparator());

sort函数会一直调用compare方法,保证数组被重新按照长度进行排序。


注意:如果Integer.compare(x, y)中的x.equals(y)==true则返回0,如果x<y则返回负数,x>y则返回正数。这个静态方法已经被添加到Java7中。千万不要计算x-y然后和x或者y比较,因为x-y可能会发生溢出。

还有一种场景就是按钮的方法回调。你把回调的处理放入实现的监听接口函数中,构造一个实例,然后将这个实例注册到按钮上:
button.setOnAction(new EventHandler<ActionEvent>) {
public void handle(ActionEvent event) {
System.out.println("Thanks for clicking");
}
});

当这个按钮被点击的时候,handle方法将被执行。

从上面几个例子中你可以看到,这些处理都是需要一大段的代码来处理。这么复杂的处理不是每个人都可以非常容易理解的,所以在Java8中添加了一个非常重大的特性,那就是Lambda表达式。

1.2 Lambda表达式语法

现在咱们再来看一下之前排序的例子:

Integer.compare(first.length(), second.length());

first和second都是字符串数组,Java是一个强类型的语言,所以我们必须这样来处理:

(String first, String second)
-> Integer.compare(first.length(), second.length());

这就是你的第一个lambda表达式了!这样的表达式非常简单。

可以看到,在这个lambda表达式中有->符号。如果这段代码不能用一个简单的表达式来展示的话,我们可以用{}来封装一段代码,比如:

(String first, String second) -> {
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}

当一个lambda表达式没有参数的时候,我们可以这样来做:

() -> { for (int i = 0; i < 1000; i++) doWorker(); }

如果一个lambda表达式中的参数可以推断出它的类型,那么我们还可以这样:

Comparator<String> comp
= (first, second) // 等价于(String first, String second)
-> Integer.compare(first.length(), second.length());

你可以再lambda表达式的参数添加标注或者final修饰符:

(final String name) -> ...

(@NonNull String name) -> ...


lambda表达式中的返回类型我们一直没有提及,那是因为在lambda上下文中,可以推断出它,比如:

(String first, String second) -> Integer.compare(first.length(), second.length())

从这里就可以看出,返回类型就是int。

1.3 功能接口

Java中已经封装了一些存在的接口代码,想Runnable和Comparator。Lambda对于这些接口是向下兼容的。

当一个单实例抽象方法的接口对象,我们可以用lambda表达式来展示出来,我们就把这个接口叫做功能接口。

为了展示功能接口,我们来看一下Arrays.sort()方法。它的第二个参数需要一个Comparator的实例,可以用lambda这样做:

Arrays.sort(words,
(first, second) -> Integer.compare(first.length(), second.length()));

和传统的内部类相比,lambda表达式可以非常高效的完成它。lambda表达式最好的理解为它是一个函数,而不是对象。

lambda的语法非常简短和简单,再来一例:

button.setOnAction(event ->
System.out.println("Thanks for clicking"));

和内部类相比,可读性大幅提升。

事实上,在Java中,你只能针对功能接口应用lambda表达式。

Java API在java.util.function中定义了一些常用的功能接口。比如,BiFunction<T, U, R>,这个接口通过参数类型T和U,返回类型R,我们可以应用在刚才的例子上:

BiFunction<String, String, Integer> comp
= (first, second) -> Integer.compare(first.length(), second.length());

当然,这里只是构造了一个比较器,只有Arrays.sort方法调用的时候才能进行排序。

1.4 方法引用

有些时候,一个方法你不得不带上一些多余的代码。比如:当你想要打印一个按钮点击之后的事件对象:

button.setOnAction(event->System.out.println(event));

如果可以只通过println方法来做的话就更nice了,比如:

button.setOnAction(System.out::println);

System.out::println表达式是一个方法引用,它等价于x->System.out.println(x).

再比如,我们想要对忽略大小写的数组进行排序:

Arrays.sort(strings, String::compareToIgnoreCase);

这些例子中,::操作符的规则为:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

前2个例子中,方法引用等价于lambda表达式中的函数参数,System.out::println等价于x->System.out.println(x),相应的,Math::pow等价于(x, y) -> Math.pow(x, y).

第三个例子中,第一个参数变成了函数的对象,String::compareToIgnoreCase等价于(x, y) -> x.compareToIgnoreCase(y).

你也可以使用this,比如:this::equals等价于x->this.equals(x),当然也可以使用super.

super::instanceMethod

举个例子:

class Greeter {
public void greet() {
System.out.println("Hello world");
}
} class ConcurrentGreeter extends Greeter {
public void greet() {
Thread t = new Thread(super::greet);
t.start();
}
}

1.5 构造引用

除了new方法,构造引用类似方法引用。举个例子,Button::new是一个Button的构造器。

List<String> labels = ...;
Stream<Button> stream = labels.stream().map(Button::new);
List<Button> buttons = stream.collect(Collections.toList());

1.6 变量域

当你在lambda中想要从闭包函数或者类中获取变量,如下:

public static void repeatMessage(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) {
System.out.println(text);
Thread.yield();
}
};
new Thread(r).start();
}

调用方式:repeatMessage("Hello", 1000);  // 在一个单独的线程里打印1000次Hello

看一下lambda表达式中的变量count和text,这些变量不是在lambda表达式中定义的。他们是方法repeatMessage的参数。

一个lambda表达式有3个要素

  1. 代码块
  2. 参数
  3. 一个空闲变量的值,这个变量不是参数且不是在这块代码里定义的

在我们的例子中,lambda表达式有2个变量,text和count。但是如果我换一种写法,如下:

public static void repeatMessage(String text, int count) {
Runnable r = () -> {
while(count > 0) {
count--;
System.out.println(text);
Thread.yield();
}
};
new Thread(r).start();
}

上面的代码有问题吗?答案是有的,因为count--;这一句。原因是不能修改获取的变量值。变化的变量在一个lambda表达式中是线程不安全的。试想一个序列的并发任务,每一个任务更新一个共享的计数器。

int matches = 0;
for (Path p : files)
new Thread(() -> {if (p has some property) matches++;}).start();
// 非法

这里的matches不是原子性的增长,所以在并发情况下无法获知它的增长。


注意:内部类可以在一个封闭的区域中获取值,Java8之前,内部类只允许获取final的本地变量。内部类可以获取任何final本地变量-任何值不变的变量。


如果matches是一个实例或者封闭类的静态变量,这里就不会再报错误了。

一个共享对象的变化是没有任何问题的,及时它是不全面的,比如:

List<Path> matches = new ArrayList<>();
for (Path p : files)
new Thread(() -> { if (p has some property) matches.add(p);}).start();
// matches变化是合法的但是不是线程安全的

在一个函数中,你不能在一个代码块中有2个相同的变量名字,比如:

Path first = Paths.get("/usr/bin");
Comparator<String> comp =
(first, second) -> Integer.compare(first.length(), second.length());
// 变量first多次定义

第一章 Lambda表达式的更多相关文章

  1. Upgrading to Java 8——第一章 Lambda表达式

    第一章 Lambda表达式 Lamada 表达式是Java SE 8中最重要的新特性,长期以来被认为是在Java中缺失的特性,它的出现使整个java 语言变得完整.至少到目前,在这节中你将学习到什么是 ...

  2. 第三章 Lambda表达式

    第三章 Lambda表达式 3.1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是“拿什么东西做什么事情”.相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函 ...

  3. Java8新特性第1章(Lambda表达式)

    在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(V ...

  4. 第一章 EL表达式常见用法

    el最常用的几种使用场景: 从配置文件中读取属性 缺失值情况下,配置默认值 el内部字符串使用String的方法 三目运算符 正则表达式 注入系统属性(system properties) 调用系统原 ...

  5. 怒学Java8系列一:Lambda表达式

    PDF文档已上传Github  Github:https://github.com/zwjlpeng/Angrily_Learn_Java_8 第一章 Lambda 1.1 引言 课本上说编程有两种模 ...

  6. 01 语言基础+高级:1-7 异常与多线程_day07 【线程池、Lambda表达式】

    day07[线程池.Lambda表达式] 主要内容 等待与唤醒案例 线程池 Lambda表达式 教学目标 -[ ] 能够理解线程通信概念-[ ] 能够理解等待唤醒机制-[ ] 能够描述Java中线程池 ...

  7. Java8函数式编程以及Lambda表达式

    第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...

  8. 328 day07线程池、Lambda表达式

    day07[线程池.Lambda表达式] 主要内容 等待与唤醒案例 线程池 Lambda表达式 教学目标 -[ ] 能够理解线程通信概念 -[ ] 能够理解等待唤醒机制 -[ ] 能够描述Java中线 ...

  9. 1.1 为什么要使用lambda 表达式

    第1章 lambda 表达式 1.1 为什么要使用lambda 表达式 1.2 lambda 表达式的语法 1.3 函数式接口 1.4 方法引用 1.5 构造器引用 1.6 变量作用域 1.7 默认方 ...

随机推荐

  1. VS2010界面主题更换全过程

    VisualStudio 2010的界面默认是蓝色的,背景是白色,字体是宋体,这些设置习惯了还好,但是可能看多了不怎么舒服.而且如果以前是用VS 6.0的知道,它使用的字体更为舒服清晰.所以,可以对V ...

  2. maven+tomcat6-maven-plugin实现热部署及调试

    maven project,特别是maven module项目默认情况下是是无法直接通过tomcat等容器部署的,如图,我要部署fastdev_web这个maven module,可以看出在tomca ...

  3. 如果Android和C#在一起?

    先看两则新闻.   一则来自新浪科技:   谷歌上诉遭拒绝 需向甲骨文支付Java使用费 大意是说,针对谷歌Android操作系统侵犯甲骨文Java知识产权的指控,美国法院最近做出了有利于甲骨文的裁决 ...

  4. RE:转:一些不常用的html代码

    1. oncontextmenu="window.event.returnvalue=false" 将彻底屏蔽鼠标右键<table border oncontextmenu= ...

  5. [转载]JQuery的Ajax跨域请求的解决方案

    今天在项目中需要做远程数据加载并渲染页面,直到开发阶段才意识到ajax跨域请求的问题,隐约记得Jquery有提过一个ajax跨域请求的解决方式,于是即刻翻出Jquery的API出来研究,发现JQuer ...

  6. PAT-乙级-1038. 统计同成绩学生(20)

    1038. 统计同成绩学生(20) 时间限制 250 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 本题要求读入N名学生的成绩,将 ...

  7. chardet安装

    1.下载 chardet-2.2.1.tar.gz (md5)   https://pypi.python.org/pypi/chardet#downloads 2.解压至C:\Python27\Li ...

  8. Side by Side Assembly介绍--manifest文件的使用

    什么是Side-by-Side Assembly? Side-by-Side Assembly(建称SxS)是微软在Visual Studio 2005(Windows 2000?)中引入的技术,用来 ...

  9. [杂题]FZU2190 非提的救赎

    中文题,题意不多说. 本来感觉很像dp 其实只要从上到下维护单调性就好了 坑是......这个oj......用cin很容易TLE...... //#include <bits/stdc++.h ...

  10. 编译qt-mobility

    因为用到了qt-mobility,必须自己编译一下,参考列出了参考资料. 参考: 1. windows下编译qt-mobility  http://hi.baidu.com/xchinux/blog/ ...