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. sql之表的表达式

    1.派生表 实质:就是特殊的子查询(将查询结果放在from后面) 含有一张Order表: 看下面的sql语句: select orderid,orderdate,custid from ( selec ...

  2. [转载].Net中如何操作IIS(源代码)

    ///***********************************************************///************** IIS控制管理类 1.0 Beta ** ...

  3. 关于ref与out的区别

    写在最前面 这几天一直在公司接受培训,都是一些基础的知识,同时也乘着这个机会巩固一下自己的基础,基础太重要了.前些时一直看的是多线程方面的知识,接下来我会写一些其他方面的知识,毕竟作为一个实习新人得和 ...

  4. php构造函数construct用法注意事项

    <?php class A { //特别注意,这里的下划线为两个 function __construct() { echo "I am the constructor of A.&l ...

  5. Java Socket实战之一 单线程通信

    本文地址:http://blog.csdn.net/kongxx/article/details/7259436 现在做Java直接使用Socket的情况是越来越少,因为有很多的选择可选,比如说可以用 ...

  6. HDU1875——畅通工程再续(最小生成树:Kruskal算法)

    畅通工程再续 Description相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现.现在政府决定大力发展百岛湖,发展首先要解决的问题当 ...

  7. Django处理文件上传File Uploads

    HttpRequest.FILES 表单上传的文件对象存储在类字典对象request.FILES中,表单格式需为multipart/form-data <form enctype="m ...

  8. 退出myeclipse 8.5配置中心

    用myeclipse 8.5没多久,进入软件中心下载插件,找不到退出按钮,唉,木想到就是一图标.

  9. java网络基本类使用(一)

    1.怎么获取ip相关信息 import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumera ...

  10. 1628. White Streaks(STL)

    1628 题意不太好理解 求横黑条 和竖黑条共有多少个 注意1*1的情况 如果横向纵向都是1*1 算为一个 否则不算 用了下vector  枚举找下 #include <iostream> ...