1. lambda表达式
1.0 引出
我们平常会使用Arrays.sort方法对数组进行排序,如果我们想自定义排序,就会使用sort的一个重载方法,a是待排序的数组,c是一个比较器,可以说是一个实现了Comparator接口的类的对象。
public static <T> void sort(T[] a, Comparator<? super T> c);
下面以按照字符串的长度从小到大排序为例看一下这个方法的使用:
首先定义Comparator的实现类:
class StringLengthCompare implements Comparator<String>{
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
然后使用sort方法进行排序
@Test
public void lambdaTest(){
String[] strs = {"Alice", "Black", "Cindy", "Mr Green", "Mike", "Zip"};
//实例化一个Comparator对象
Comparator<String> comparator = new StringLengthCompare();
Arrays.sort(strs, comparator);
System.out.println(Arrays.toString(strs));
}
排序结果:

考虑上面的做法,功能可以实现,没问题,但是如果想要用多种方式排序,那么就要写多个实现类,这样的做法让代码看起来不是那么简洁,或许可以说,我们可以使用匿名类实现这个接口,但这样又会有代码难以复用的问题,所以我们引入了lambda表达式来解决这个问题,做法如下,这样看起来比上面的做法简洁很多:
@Test
public void lambdaTest(){
String[] strs = {"Alice", "Black", "Cindy", "Mr Green", "Mike", "Zip"};
//lambda表达式
Comparator<String> comparator = (o1, o2) -> o1.length() - o2.length();
Arrays.sort(strs, comparator);
System.out.println(Arrays.toString(strs));
}
下面我们来详细介绍一下lambda表达式
1.1 简介
-
定义:lambda表达式是一个可执行的代码块,可以重复执行一次或多次
-
作用
- 使代码更简洁,更易读
- 使代码延迟执行(deferred execution),这个作用将在之后进行介绍
-
组成
- 参数
- 一个代码块
- 自由变量的使用,在1.2.2中说明
1.2 语法
1.2.1 基本组成
- (参数列表) -> 代码块
//一个字符串比较器,按照字符串的长度比较大小,
Comparator<String> comparator = (String o1, String o2) -> {
return o1.length() - o2.length();
};
- 如果编译器可以推导出参数的类型,则参数列表中可以不加类型
//下面这个比较器可以推导出o1, o2是字符串类型,可省略,Comparator<T> -> Comparator<String>
Comparator<String> comparator = (o1, o2) -> {
return o1.length() - o2.length();
};
- 如果代码块中只有一个语句,则可以省略“{}”
Comparator<String> comparator = (o1, o2) -> o1.length() - o2.length();
说明:如果加上{},若有返回值,则必须写return xxx;如果写成上面这种形式,lambda表达式会根据上下文推导出这个语句是否作为返回值。
Comparable的原型方法如下,返回值为int,所以会推出o1.length() - o2.length()的结果作为返回值返回
int compare(T o1, T o2);
- 如果参数列表只有一个参数,则可以省略()
ActionListener listen = e ->
System.out.println(e);
- 如果无参数,则必须写上()
() -> {
for(int i = 0; i < n; ++ i){
System.out.println(i);
}
}
1.2.2 自由变量的使用
- 定义:自由变量是非参数而不在代码块中定义的变量【这两种变量该咋用咋用】,如类的成员变量,方法的参数等
- 使用限制:在lambda中使用的自由变量不可以被修改,即初始化完成后不可以有任何其它的修改操作,这种变量有个新的名字叫做:事实最终变量(effectively final)
- 举例说明:
两处Error的地方都会出现*Variable used in lambda expression should be final or effectively final*的错误
@Test
public void lambdaTest(){
//初始化一个时间
LocalDate date = LocalDate.of(2004, 10, 1);
//Objects.requireNonNullElseGet这个方法的作用是,如果第一个参数不为null,则返回第一个参数,如果第一个参数是空对象,则返回默认的值,即第二个参数返回或表示的对象
LocalDate outPutDate = Objects.requireNonNullElseGet(null, () ->{
//date = date.plusDays(10);//Error
return date;
});
//date = date.plusDays(10); //Error
System.out.println(outPutDate);
}
为什么要这些使用限制:并发执行时不安全
1.3 函数式接口
1.3.1 理解
-
定义:只有一个抽象方法的接口,需要这种接口的对象时,可以提供一个lambda表达式,这种接口叫做函数式接口
-
为什么只有一个抽象方法
说一下我个人的理解:lambda表达式相当于是对一个接口中的抽象方法的实现,方法名和参数列表唯一标识了一个方法,而lambda表达式只有参数列表无方法名,如果接口里面有多个抽象方法且参数列表相同,则编译器不知道是实现的哪一个方法,导致错误。
经过测试,如果接口中有两个方法,则会出现*
Multiple non-overriding abstract methods found in interface xxx的错误* -
为什么强调抽象方法
因为lambda可以看成是对抽象方法的实现方法,接口可以对方法提供默认的实现方式。
-
lambda表达式做的事是转化为函数式接口,不能把lambda表达式赋值给Object类型的变量
1.4 方法引用
1.4.1 概念
- 定义:方法引用指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法
- 要求:只有当lambda表达式的体只调用一个方法不做其它操作时,才能把lambda表达式写成方法引用
- 方法引用不能独立存在,总是会转换为函数式接口的实例
1.4.2 用法
从以下可以看出,方法引用可以简化lambda表达式的书写,可以增强代码复用,如比较器调用compareToIgnoreCase方法
object::instanceMethod,等价于向lambda表达式传递参数的lambda表达式//这个类可以设置定时任务,每隔1000秒执行一次System.out::println //方法引用System.out::println <=> e -> System.out.println(e) Timer timer = new Timer(1000, System.out::println);Class::instanceMethod,这种第一个参数会成为方法的隐式参数//比较器 //String::compareToIgnoreCase <=> (o1, o2) -> o1.compareToIgnoreCase(o2) Arrays.sort(strs, String::compareToIgnoreCase);Class::staticMethod,所有的参数都传到静态方法
//Integer::valueOf <=> x -> Integer.valueOf(x)
1.4.3 构造器引用
构造器引用与方法引用类似,只不过方法是new,在使用stream、collect时可能会使用。
1.5 lambda表达式延迟执行
1.5.1 理解
- 延迟执行就是不立刻执行,如果想立刻执行,直接写出来即可,无需用lambda表达式包装
- 为什么要延迟执行
- 特定情况下执行代码,非必要不执行,减少资源浪费
- 多次执行代码
- 在一个单独的线程中运行代码
1.5.2 举例
@Test
public void lambdaTest1(){
LocalDate day = LocalDate.now();
// LocalDate day = null;
Supplier<? extends LocalDate> supplier = () -> {
System.out.println("supplier 执行了");
return LocalDate.of(1970, 1, 1);
};
LocalDate localDate = Objects.requireNonNullElseGet(day, supplier);
System.out.println("localDate = " + localDate);
}
@Test
public void lambdaTest6(){
LocalDate day = LocalDate.now();
// LocalDate day = null;
LocalDate defaultDate = LocalDate.of(1970, 1, 1);
LocalDate localDate = Objects.requireNonNullElse(day, defaultDate);
System.out.println("localDate = " + localDate);
System.out.println("defaultDate = " + defaultDate);
}
对于lambdaTest1,day不是null,结果如下

day是null,结果如下:

对于lambdaTest2,不管day是不是null,结果如下:

在1中,如果用不到默认日期,则不会生成一个日期为1970-01-01的对象,用到了则加载,2中只要方法被调用则会造一个1970-01-01的对象,如果实际情况中用到默认情况的次数很少,则可以使用lambda表达式的方式提供,不会产生无用的实例对象,减少资源浪费。
1.6 自定义处理lambda表达式
1.6.1 使用Java提供的函数式接口
class LambdaExample{
/**
* 把list中的奇数变成其相反数
* 模仿定义
* boolean removeIf(Predicate<? super E> filter)
* @param list 待处理的集合
* @param filter 检测是否是奇数,如果是,则返回true
*/
public void usePredicate(List<Integer> list, Predicate<Integer> filter){
System.out.println("处理前的list = " + list);
for (int i = 0; i < list.size(); ++ i) {
if(filter.test(list.get(i))){
list.set(i, -list.get(i));
}
}
System.out.println("处理后的list = " + list);
}
}
@Test
public void testMakeLambda1(){
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6);
LambdaExample lambdaExample = new LambdaExample();
lambdaExample.usePredicate(list, integer -> integer % 2 == 1);
}
结果:

1.6.2 使用自定义的函数式接口
interface LExample{
void accept(int value);
}
class LambdaExample{
public void repeat(int n, LExample action){
System.out.println("action = " + action);
for (int i = 0; i < n; ++i){
action.accept(2);
}
}
}
//延迟加载->重复执行一段代码
@Test
public void testMakeLambda(){
LambdaExample lambdaExample = new LambdaExample();
lambdaExample.repeat(3, e -> {
System.out.println("value = " + e);
});
}
结果:

1.7 总结
- lambda表达式就是一个可执行的代码块,可以重复执行一次或多次,可以让代码看起来更简洁,实现延迟加载。
- lambda表达式可以转换成函数式接口,不可以赋值给Object。
- lambda表达式可以看成是对一个接口中唯一的抽象方法的实现,作为参数传递到用到这个接口的方法中。当这个方法使用接口中的方法时,回调lambda表达式执行其中的代码。

2233

被折叠的 条评论
为什么被折叠?



