一、Lambda表达式的产生
Lambda表达式是作为行为参数化的一种优化,以下将会顺序介绍行为参数化,匿名类等概念,再引出Lambda表达式。
1、一等值
若要真正认识行为参数化带来的便利,首先要认识一下一等值
一等值,又称作java的一等公民,即java可以操作的值,可以它的包含关系如下:
java可以操作的值究竟是什么意思呢?狭义上来说,是可以作为参数传递给方法的值。
2、二等值
有了一等值,自然就有二等值,二等值即指
有助于表示值的结构,但在程序执行期间不能传递的结构
通俗意义上来说就是不能作为参数传递的结构,例如类和方法
类内这些成员变量是用于表示值,所以类是一个用于表示值的结构,因为类可以实例化来生产值,同理,方法也是
总结:
3、行为参数化
我们如果需要让同一个方法实现不同的需求,我们可以将代码以接口实例的形式作为参数传入,称为行为参数化,这也是将二等值变为一等值的方法。
下面看一个行为参数化的例子:
问题:假如果农需要找出所有的绿苹果;也要找出所有的重量大于150的苹果。
分析:两个需求,都是迭代每一个苹果,再根据判断条件选出每一个符合条件的苹果。
这样,我们先定义一个迭代每一个苹果的方法,参数需要传入苹果集合和判断条件(行为参数化)
public static List<Apple> filter(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}
这样,我们需要定义一个ApplePredicate 接口,其有两个实现类,分别判断绿苹果和重量大于150.
public interface ApplePredicate{
boolean test (Apple apple);
}
public class AppleWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getColor() == Color.GREEN;
}
}
进行调用
public static void main(String... args) {
//省略苹果集合的创建
List<Apple> heavyApples = filter(inventory, new AppleWeightPredicate());
System.out.println(heavyApples);
}
这样就实现了把判断这个行为作为参数传入去,从而进行各种判断。
显然,这样的做法是非常笨重的。所以一般不会这样做,我们往往用匿名内部类来解决这些笨重的代码,也就是不需要再额外显式定义实现类。
4、匿名内部类
匿名内部类是行为参数化的一个改进,它不再需要显式实现类,只需对接口方法进行实现即可
下面这个例子是对上面例子的改进:
在调用的时候,匿名内部类定义判断行为
List<Apple> redApples = filter(inventory,new ApplePredicate(){
public boolean test(Apple a){
return RED.equals(a.getColor());
}
});
但是,这样子看起来还是很复杂,并且一下子还是比较困难看出这是表示什么意思,所以在java8中开发出了Lambda表达式,这里先放上用Lambda的形式,可见其比匿名类简洁了许多
List<Apple> redApples =
filter(inventory,(Apple a)-> RED.equals(a.getColor()));
二、Lambda表达式详解
另一个例子:
假如我们需要根据苹果重量进行排序,我们可以调用list.sort(Comparator c)方法,其参数需要是一个Comparator接口,在java8前,我们只能用匿名内部类的方式来定义比较条件。
appleList.sort(new Comparator<Apple>(){
public int compare(Apple a1,Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
但在Java8后,引入了Lambda表达式,以上的写法可大大简化,如下
appleList.sort(
(Apple a1,Apple a2)-> {a1.getWeight().compareTo(a2.getWeight())}
);
接下来我们会细细分析,但有一点值得注意的是,Lambda表达式是以Comparator实现类的形式作为参数传入的。
1、lambda表达式的结构
- 参数列表:就是上例的
(Apple a1,Apple a2)
- 箭头: 即
->
- Lambda主体,对应上例的
{a1.getWeight().compareTo(a2.getWeight())}
Lambda表达式的格式
(i)参数列表
- 如果参数类型可以通过上下文(目标类型)进行类型推断出,那么参数类型可以省略
例如
Comparator<Apple> c = (Apple a1,Apple a2) -> {a1.getWeight().compareTo(a2.getWeight())};
这个时候,因为目标类型是Comparator<Apple>
,所以在参数列表无须指定参数类型,因为java可以推断得出。如下。
Comparator<Apple> c = (a1,a2) -> {a1.getWeight().compareTo(a2.getWeight())};
- 参数列表可以为空,主体也可以为空,即如下表达式是成立的
() -> {}
(ii)Lambda主体
- 标准上来说,主体是一个代码块,可以包含多条语句
例:
() -> {
System.out.println("Hello");
System.out.println("World");
}
- Lambda表达式有返回值,且没有其他语句,可省略花括号和return关键字
例:() -> "Hello"
等价于() -> {return "Hello";}
2、Lambda表达式的使用详解
(i)函数式接口的引入
(可以先看下面图解,这样好理解一点)
再回到这一章最开始给出的例子,我们想对苹果根据重量进行排序。
appleList.sort(Comparator c) //Comparator 也是函数式接口
在这个过程中,我们需要进行迭代,在迭代中对每两个苹果进行比较,比较的标杆是什么呢?就是重量,我们需要将比较重量这一行为作为参数传递给sort()。
而sort()怎么知道传进来的参数是什么呢?它就使用了一个接口来当作占位符,这种接口只有一个未实现的方法(默认方法除外),这种接口被称作函数式接口。
我们可以联想一下数学上的函数就可以理解它为什么叫函数式接口。
- 函数式接口只有一个方法,有输入,有输出(空也算作一种输入输出)
int Comparator<T>(T t1,T t2);
- 数学上的函数也是,接收输入,然后输出
y = f( x, X )
- 找一下对应关系即可知道,y就是int ,t1和t2分别与x和X对应,Comparator自然就与f()对应
- 而Lambda表达式也可以等同于
f(x , X) = x^2 + X^3
这种表达式
至于为什么函数式接口只能有一个方法,因为一个接口只能对应一个f(),如果有多个方法,调用f()时,java就不知道究竟要找哪一个方法执行。
下面给出图解(这个图解针对的是选择苹果的例子)
(ii)函数描述符
函数式接口的抽象方法的签名必须要与Lambda表达式的签名匹配。我们把这种抽象方法的签名叫做函数描述符。
所谓签名就是包括参数和返回的一个集合
例如:
函数式接口T Supplier<T>()
的方法签名是() -> T
Lambda表达式() -> "Hello"
的签名() -> String
在编译过程中,java编译器会对Lambda表达式的签名进行类型检查,看看是否符合接口方法的签名。
(iii)类型检查流程
这里引用《Java实战》的一篇插图,里面讲解的非常清楚
注:此文是阅读《Java实战》的学习总结,若希望深入学习,请自行购买《Java实战》学习。