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. apache本地和局域网访问设置

    apache本地和局域网访问设置 最近做项目需要同事ajax发项目给我,因为是测试环境,所以需要能访问我的服务器.我服务器直接用的wampserver.因为没有接触过,所以百度了一下,都是没有成功,后 ...

  2. 监控SQL Server的job执行情况

    在服务器没有设置发邮件并且不允许发邮件的情况下, 可以通过下列语句来检查SQL Server 的job的执行情况 select top 150 a.run_date,a.run_time, b.nam ...

  3. Code for the Homework1

    作业要求: http://www.cnblogs.com/bingc/p/4919692.html 代码(未使用Eigen): #include <iostream> #include & ...

  4. ios 中的小技巧 - 总有你想要的 一

    UITableView的Group样式下顶部空白处理 在viewWillAppear里面添加如下代码: //分组列表头部空白处理 CGRect frame = myTableView.tableHea ...

  5. 2017年iOS应用将强制使用HTTPS安全加密-b

    6月14日,WWDC 2016苹果开发者大会上,苹果在讲解全新的iOS10中提到了数据安全这一方面,并且苹果宣布iOS应用将从2017年1月起启用名为App Transport Security的安全 ...

  6. Datatables中文API——回调函数

    fnCookieCallback:还没有使用过 $(document).ready(function () { $('#example').dataTable({ "fnCookieCall ...

  7. button以回车方式提交

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. python url编码

    1.quote:使用适合URL内容的转义序列替换String中的特殊字符. 2.quote_plus:调用quote并使用“+”替换所有空格 3.unquote:使用转义字符的单字符对应物替换'%xx ...

  9. Interface Serializable

    public interface Serializable Serializability of a class is enabled by the class implementing the ja ...

  10. IntelliJ Idea12 破解码与中文乱码配置

    user name:JavaDeveloper serial number:92547-KY2BB-QZ0S1-PEZCV-HUT8Q-6RYY4        会出现Ok可以点击就会将软件 安装后, ...