第一章 Lambda表达式
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个要素
- 代码块
- 参数
- 一个空闲变量的值,这个变量不是参数且不是在这块代码里定义的
在我们的例子中,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表达式的更多相关文章
- Upgrading to Java 8——第一章 Lambda表达式
第一章 Lambda表达式 Lamada 表达式是Java SE 8中最重要的新特性,长期以来被认为是在Java中缺失的特性,它的出现使整个java 语言变得完整.至少到目前,在这节中你将学习到什么是 ...
- 第三章 Lambda表达式
第三章 Lambda表达式 3.1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是“拿什么东西做什么事情”.相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函 ...
- Java8新特性第1章(Lambda表达式)
在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(V ...
- 第一章 EL表达式常见用法
el最常用的几种使用场景: 从配置文件中读取属性 缺失值情况下,配置默认值 el内部字符串使用String的方法 三目运算符 正则表达式 注入系统属性(system properties) 调用系统原 ...
- 怒学Java8系列一:Lambda表达式
PDF文档已上传Github Github:https://github.com/zwjlpeng/Angrily_Learn_Java_8 第一章 Lambda 1.1 引言 课本上说编程有两种模 ...
- 01 语言基础+高级:1-7 异常与多线程_day07 【线程池、Lambda表达式】
day07[线程池.Lambda表达式] 主要内容 等待与唤醒案例 线程池 Lambda表达式 教学目标 -[ ] 能够理解线程通信概念-[ ] 能够理解等待唤醒机制-[ ] 能够描述Java中线程池 ...
- Java8函数式编程以及Lambda表达式
第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...
- 328 day07线程池、Lambda表达式
day07[线程池.Lambda表达式] 主要内容 等待与唤醒案例 线程池 Lambda表达式 教学目标 -[ ] 能够理解线程通信概念 -[ ] 能够理解等待唤醒机制 -[ ] 能够描述Java中线 ...
- 1.1 为什么要使用lambda 表达式
第1章 lambda 表达式 1.1 为什么要使用lambda 表达式 1.2 lambda 表达式的语法 1.3 函数式接口 1.4 方法引用 1.5 构造器引用 1.6 变量作用域 1.7 默认方 ...
随机推荐
- apache本地和局域网访问设置
apache本地和局域网访问设置 最近做项目需要同事ajax发项目给我,因为是测试环境,所以需要能访问我的服务器.我服务器直接用的wampserver.因为没有接触过,所以百度了一下,都是没有成功,后 ...
- 监控SQL Server的job执行情况
在服务器没有设置发邮件并且不允许发邮件的情况下, 可以通过下列语句来检查SQL Server 的job的执行情况 select top 150 a.run_date,a.run_time, b.nam ...
- Code for the Homework1
作业要求: http://www.cnblogs.com/bingc/p/4919692.html 代码(未使用Eigen): #include <iostream> #include & ...
- ios 中的小技巧 - 总有你想要的 一
UITableView的Group样式下顶部空白处理 在viewWillAppear里面添加如下代码: //分组列表头部空白处理 CGRect frame = myTableView.tableHea ...
- 2017年iOS应用将强制使用HTTPS安全加密-b
6月14日,WWDC 2016苹果开发者大会上,苹果在讲解全新的iOS10中提到了数据安全这一方面,并且苹果宣布iOS应用将从2017年1月起启用名为App Transport Security的安全 ...
- Datatables中文API——回调函数
fnCookieCallback:还没有使用过 $(document).ready(function () { $('#example').dataTable({ "fnCookieCall ...
- button以回车方式提交
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- python url编码
1.quote:使用适合URL内容的转义序列替换String中的特殊字符. 2.quote_plus:调用quote并使用“+”替换所有空格 3.unquote:使用转义字符的单字符对应物替换'%xx ...
- Interface Serializable
public interface Serializable Serializability of a class is enabled by the class implementing the ja ...
- IntelliJ Idea12 破解码与中文乱码配置
user name:JavaDeveloper serial number:92547-KY2BB-QZ0S1-PEZCV-HUT8Q-6RYY4 会出现Ok可以点击就会将软件 安装后, ...