lambda表达式
1.lambda表达式的定义
可以把lambda表达式理解为一种可传递的匿名函数,有参数列表、函数主题,返回类型,可能还有一个可抛出的异常列表
匿名:不需要拥有一个明确的名称
函数:不属于某个特定的类,但和方法一样拥有参数列表、函数主题,返回类型,可能还有一个可抛出的异常列表。
传递:可以作为参数传递给方法或存放再变量中
简洁: 无需像匿名类那样写很多模板代码
1.1用Comparator排序
Java中List(实际是Collections)有一个sort方法,可用java.util.Comparator进行参数化,如这里有一个Comparator,实现按照苹果的重量排序
Comparator<Apple> byWeight = new Comparator<Apple>(){
public int compare(Apple a1,Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
apples.sort(byWeight);
采用lambda表达式的话
apples.sort((Apple a1,Apple a2)->
a1.getWeight().compareTo(a2.getWeight())
);
2.使用lambda表达式
2.1函数式接口
定义:只定义了一个抽象方法的接口
举例:Comparator,Runnable
Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。即Lambda表达式可以作为一个函数式接口的具体实现。
函数描述符:接口的抽象方法。
函数式接口中定义了一个抽象方法,这个抽象方法的签名(入参,返回)就是Lambda表达式的签名。
2.2编写lambda表达式
1.行为参数化
2.使用函数式接口传递行为
3.执行一个行为
4.传递lambda
直接以筛选苹果为例
目的:筛选出所有重量大于150g的苹果
1.行为参数化
显然,这里的行为指的是筛选重量大于150g的苹果,即
return apple.getWeight()>150;
因此,这里筛选苹果的代码应为
List<Apple> resultList5 = FilterApple.filterApplesByPredicate(apples,(Apple apple)-> apple.getWeight()>150);
for (Apple apple : resultList5){
System.out.println(apple.getId());
}
调用的筛选方法为
public static List<Apple> filterApplesByPredicate(List<Apple> apples, ApplePredicate predicate){
List<Apple> result = new ArrayList<>();
for(Apple apple : apples){
if(predicate.test(apple)){
result.add(apple);
}
}
return result;
}
可以看到,在filterApplesByPredicate()方法中参数ApplePredicate,我们传入的是
(Apple apple)-> apple.getWeight()>150
2.使用函数式接口传递行为
使用lambda表达式,势必要有对应的函数式接口(只有一个抽象方法的接口),这里我们定义的函数式接口为ApplePredicate
@FunctionalInterface
public interface ApplePredicate {
boolean test(Apple apple);
}
3.执行一个行为
可以看到,在filterApplesByPredicate()方法中,我们调用了这一行为参数,即
predicate.test(apple)
调用ApplePredicate.test()对Apple进行处理
4.传递lambda
实际上,在开头我们就是一次成功的传递lambda。
List<Apple> resultList5 = FilterApple.filterApplesByPredicate(apples,(Apple apple)-> apple.getWeight()>150);
同理,进行不同的筛选肯定还有
List<Apple> resultList5 = FilterApple.filterApplesByPredicate(apples,(Apple apple)-> apple.getWeight()<100);
List<Apple> resultList5 = FilterApple.filterApplesByPredicate(apples,(Apple apple)-> apple.getColor().equals("red")); //字符串比较时最好是"red"等确保有值的String写在前面调用equals方法,避免NPE
2.3使用函数式接口
首先了解一些函数式接口
函数式接口 | 参数类型 | 返回类型 | 描述 |
---|---|---|---|
Supplier | 无 | T | 接收一个T类型的值 |
Consumer | T | 无 | 处理一个T类型的值 |
BiConsumer<T, U> | T,U | 无 | 处理T类型和U类型的值 |
Predicate | T | boolean | 处理T类型的值,并返回true或者false. |
ToIntFunction | T | int | 处理T类型的值,并返回int值 |
ToLongFunction | T | long | 处理T类型的值,并返回long值 |
ToDoubleFunction | T | double | 处理T类型的值,并返回double值 |
Function<T, R> | T | R | 处理T类型的值,并返回R类型值 |
BiFunction<T, U, R> | T,U | R | 处理T类型和U类型的值,并返回R类型值 |
BiFunction<T, U, R> | T,U | R | 处理T类型和U类型的值,并返回R类型值 |
UnaryOperator | T | T | 处理T类型值,并返回T类型值, |
BinaryOperator | T,T | T | 处理T类型值,并返回T类型值 |
如果写过stream的话,可以看到实际上stream里很多操作都是通过上述函数式接口实现的,在这里简单列几个
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
void forEach(Consumer<? super T> action);
这里,我们用stream简单写一下筛选出重量大于150的并打印他们。
apples.stream().filter(apple->apple.getWeight()>180)
.forEach(apple -> {System.out.println(apple);});
在这个stream计算里,我们调用了2个stream的操作:filter(),forEach()
从上面这两个方法的定义可以看出
fiter方法接收一个Predicate接口行为作为参数,
forEach方法接收一个Consumer接口行为作为参数
这一个stream通过调用2个函数式接口实现了我们的编码目标。
2.4 类型检查、类型推断以及限制
2.4.1 类型检查
Lambda的类型从使用Lambda啥的上下文推断出来。上下文(接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。
以2.2表达式为例
List<Apple> resultList5 = FilterApple.filterApplesByPredicate(apples,(Apple apple)-> apple.getWeight()>150);
使用Lambda的上下文是什么呢?首先我们看一下filterApplesByPredicate的定义
public static List<Apple> filterApplesByPredicate(List<Apple> apples, ApplePredicate predicate)
显然,目标类型为ApplePredicate predicate
而这个ApplePredicate 接口的抽象方法为
boolean test(Apple apple);
接收一个Apple,返回一个boolean,函数描述符 Apple ->boolean 匹配lambda的签名,即接收一个Apple,返回boolean,因为代码类型检查没问题。
2.4.2 同一个Lambda表达式,不同的函数式接口
由上一小节可知lambda的类型检查是通过目标类型的校验完成的,显然,只要满足类型检查,lambda可以用于不同的函数式接口。
注:如果一个lambda表达式的主体是一个语句表达式,他就和一个返回void的函数描述符兼容
2.4.3类型推断
上述的lambda表达式还可以写成这样
List<Apple> resultList5 = FilterApple.filterApplesByPredicate(apples,apple-> apple.getWeight()>150);
比较不同,可以看到从
(Apple apple)-> apple.getWeight()>150
变为
apple-> apple.getWeight()>150
对于后者,java编译器从上下文(目标类型)推断出用什么函数式接口来配合lambda表达式。
2.4.4 使用局部变量
lambda可以使用自由变量,,即可以在主体中引用实例变量和静态变量,但局部变量必须显式声明为final,或者事实上fianl,就是说lambda表达式只能捕获指派给他们的局部变量一次。
2.5方法引用
方法引用可以重复使用现有的方法定义,并且像lambda一样传递它们。
(Apple a) -> a.getWeight() =================> Apple::getWeight
(String s) -> System.out.println(s) =============> System.out::println
2.5.1如何构建方法引用
方法引用主要有3类
(1) 指向静态方法的方法引用。
Integer.parseInt(String s) ============> Integer::parseInt
(2) 指向任意类型实例方法的方法引用。
String.length() =================> String::length
(3)指向现有对象的实例方法的方法引用
apple.getWeight() =============> Apple::getWeight