《Java核心技术卷I》lambda表达式 笔记
lambda表达式
Lambda 表达式(lambda expression)是一个匿名函数,即没有函数名的函数。不要将其看成对象(原因)
1. 为什么引入lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
其作用是,将某段代码块传递到某个对象(如一个定时器,或者是Arrays的sort
方法),这个代码块将在某个时间调用。
一般来说,在java中传递代码块的方式是:设计一个对象,并将代码块封装成该对象的一个方法。通过对象传递代码块
2. lambda表达式语法
基本语法
以Comparator为例:此处提供一个比较器的实现
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
Arrays.sort(strings, new LengthComparator);
可以看到,核心的代码就是first.length() - second.length()
,那么这里的first和``second
是什么?它们都是String,java是强类型语言,我们需要声明first
和second
的类型为String:
(String first, String second) -> first.length() - second.length()
这就是一个lambda表达式,它就是一个代码块以及必须传入的代码的变量规范。
可以将其赋值给Comparator对象,因为Comparator是一个函数式接口:
Comparator<String> comp =
(String first, String second) -> first.length() - second.length();
其他要求
-
如果要传入的代码块有多行,可以使用
{}
-
如果可以推导出lambda表达式的参数的数据类型,那么可以省略这些数据类型:
(first, second) -> first.length() - second.length()
-
即使没有参数需要传入,也需要提供空括号(就像无参方法一样):
() -> { for (int i =0; i < 10; i++) { System.out.println(i); } }
-
如果方法只有一个参数,且这个参数的类型可以推导得出,那么可以省略括号
ActionListener listener = event -> { System.out.println("loger"); } // 相当于 (event) -> { ... }
-
无需指定表达式的返回值类型
-
入股一个lambda表达式只在某些分支返回一个值,而在另外的分支不返回值,那这是不合法的。
-
lambda表达式只能引用标记了
final
的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
如果lambda表达式引用了不为final
的局部变量,那么该变量必须不可被后面的代码修改(即隐性的具有final
的语义) -
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量
3. lambda使用示例
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
没有使用Lambda的老方法:
button.addActionListener(new ActionListener(){ // 匿名内部类
public void actionPerformed(ActionEvent actionEvent){
System.out.println("Action detected");
}
});
Runnable runnable1 = new Runnable(){
@Override
public void run(){
System.out.println("Running without Lambda");
}
};
使用后:
button.addActionListener( actionEvent -> { // lambda表达式
System.out.println("Action detected");
});
Runnable runnable2 = () -> System.out.println("Running from Lambda");
4. 函数式接口
java有很多封装代码块的接口,如ActionListener和Comparator,这些接口与lambda表达式是相兼容的。
对于只有一个抽象方法的接口,需要这个接口的对象时,可以提供一个lambda表达式来代替。这种接口被称为函数式接口 (functional interface)
也就是说,lambda表达式可以赋值给只有一个抽象方法的接口。由于Object不是函数式接口,所以不能将lambda表达式赋值给Object类型的变量。由此可见lambda表达式不是对象,它是一个匿名函数。
Arrays.sort
方法的第一个参数是数组,第二个参数就是Comparator
,可以提供一个lambda表达式代替:
Comparator<String> comp =
(String first, String second) -> first.length() - second.length();
Arrays.sort(strings, comp);
// 等价于
Arrays.sort(strings,
(first, second) -> first.length() - second.length());
在底层,sort方法会接收实现了Comparator<String>
的某个类的对象,在这个对象上调用compare方法来指向这个lambda表达式的代码块。
ArrayList类有一个removeIf
方法,它的参数就是一个Predicate接口,这个接口专门用来传递lambda表达式:
public interface Predicate<T> {
boolean test(T t);
}
list.removeIf(e -> e == null); // 删除list中所有null值
5. 变量作用域
事实最终变量
lambda表达式只能引用标记了final
的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。如果lambda表达式引用了不为final
的局部变量,那么该变量必须不可被后面的代码修改(即隐性的具有 final
的语义)
这是为啥捏?你看看下面这段代码:
public static void repeatMessage(String text, int delay) {
ActionListener listener = event -> {
Systen.out.println(text); // 注意这里
}
new Timer(delay, listener).start();
}
仔细观察,lambda表达式可能会在repeatMessage
方法执行很久之后才开始运行,而这时这个参数变量已经不存在了(因为是形参,方法执行完毕后被回收)。而如果想要立即执行代码,则没有必要封装为lambda表达式,直接写入代码就好了鸭
在lambda表达式中,有以下3个部分:
- 一个代码块
- 参数
- *变量的值。这里指不是lambda的参数且不在lambda中定义的变量
所以,在java中,lambda表达式只能引用值不会改变的变量,即*变量的值不能改变。
原因是:如果在lambda表达式中更改变量,并发执行多个动作时就会不安全。
实际上,lambda表达式中捕获的变量必须是事实最终变量(effectively final)。这是指:这个变量初始化之后就不会再为它赋新值。
public static void countDown(int start, int delay) {
int num = 1;
num++;
for (int i = 0; i < 10; i++) {
int finalI = i;
ActionListener listener = e -> {
start--; // 出错
// Variable used in lambda expression should be final or effectively final
num++; // 可行
System.out.println(i); // 出错,i不是事实最终变量
System.out.println(finalI); // 可行, finalI是事实最终变量
};
}
}
lambda的作用域
lambda表达式的体(代码块)与被嵌套的块具有相同的作用域。所以在lambda表达式当中不允许声明一个与局部变量同名的参数或者局部变量
在lambda表达式中使用this
关键字,会调用当前域的对象,而不是lambda代表的对象。
对于下面的代码,lambda表达式被嵌套在init
方法的域中,与出现在该方法的其他位置一样,this
代表调用init
方法的Application实例对象:
public class Application {
public void init() {
ActionListener listener = e -> {
// 调用Application的toString方法,而不是ActionListener的toString方法
System.out.println(this.toString());
}
}
}
6. 处理lambda表达式
lambda表达式的特点是延迟执行。毕竟如果要立即执行,完全可以直接写入代码而不用写成lambda表达式中。
用到lambda表达式的地方有:
- 在单独一个线程中运行代码
- 多次运行代码
- 在算法适当的位置运行代码(排序)
- 发送某种情况时运行代码(监听器)
- 只在必要时运行代码
本文地址:https://blog.csdn.net/mrhanzhou5273/article/details/114337748