文章目录
内容概要
本章内容主要由4部分组成:
- lamda的使用方法
- 类型检查、类型推断及限制
- 方法引用
1. lamda的使用方法
1.1 lamda表达式由三个部分组成
- 参数列表(语法:(类型 变量名),如果仅有一个变量,基于lambda的类型推断,可省略类型,如:(e)->e.getValue())
- 箭头
- lamda主体
1.2 函数式接口
函数式接口是lamda表达式能够应用的地方。所谓函数式接口,即只定义一个抽象方法的接口,比如:
Comparator接口:
public interface Comparator<T>{
int compare(T o1,T o2);
}
Runnable接口:
public interface Runnable<T>{
void run();
}
为了更好地标记函数式接口,java提供了一个注解@FunctionalInterface,该注解可以帮助我们在编译时期检查我们的函数式接口是否正确。实际上,我们传入的lamda表达式即函数是接口中抽象方法的实现。
1.3 java8提供的常用函数式接口
接口名称 | 含义 | 使用场景 | lamda示例 |
---|---|---|---|
Predicate<T> | 接受泛型对象T并返回一个boolean | boolean表达式 | (List<String> list)->list.isEmpty() |
Consumer<T> | 接受泛型对象T,没有返回(void) | 消费一个对象 | (String s)->System.out.println(s) |
Function<T,R> | 接受泛型对象T,返回泛型对象R | 从一个对象中提取 | (String s)->s.length() |
Supplier<T> | 不接受对象,返回泛型对象R | 创建对象 | ()->new String("hello,world") |
BinaryOperator<T,T> | 传入两个泛型对象T,返回一个泛型对象T | 合并两个对象 | (int a,int b)->a+b |
这里需要介绍一点:
众所周知,Java的泛型只能绑定到引用类型,那么我们在使用函数式接口的过程当中,难道还要在使用之前对基本类型进行装箱吗?当然不用,java为了方便我们将基本类型应用到lamda当中,提出了类型特化技术,说白了,就是专门提供一系列函数式接口,这些函数式接口的接收参数都不使用泛型,而是使用特定的基本类型,比如IntFunction<R>,该函数式接口就仅接受int型参数,返回泛型R对象。
2.类型检查、类型推断及限制
2.1 类型检查
我们在使用lamda的时候,并没有指定函数式接口,那么lamda是如何工作的?lamda通过上下文获取到它所需要的类型,即目标类型。
举个例子:
List<Apple> heavierThan150g=filter(inventory,(Apple a)->a.getWeight()>150);
在这个语句中,lamda主要做了以下工作:
- 找到该方法使用的函数式接口的抽象方法:
filter(List<Apple> inventory,Predicate<Apple> p);
可以看到,predicate的参数类型绑定到了Apple,即这段代码中,lamda的目标类型是Apple
- 找到 Predicate接口的抽象方法
@FunctionInterface
public interface Predicate<T>{
boolean test(T t);
}
- 根据predicate接口入参和抽象方法的出参对lamda的类型检查提供依据
这个过程即lamda的类型检查。值得一提的是,只要符合目标类型,同一个lamda表达式可以应用在不同的函数式接口中。
2.2 类型推断
lamda表达式还可以通过上下文对传入参数的类型进行类型推断,这就可以进一步简化我们的代码:
Comparator<Apple> c=(Apple a1,Apple a2)->a1.getWeight().compareTo(a2.getWeight());
简化后:
Comparator<Apple> c=(a1,a2)->a1.getWeight().compareTo(a2.getWeight());
2.3 对局部变量的限制
lamda中使用局部变量,需要将局部变量事先声明为final才行,这是lamda以及匿名内部类都会有的对局部变量的限制。究其原因,有两点:
- lamda中的操作会在一个单独的线程中完成。局部变量保存在私有的线程栈中,如果允许捕获局部变量,那么lamda就会存在线程不安全的问题;
- 作为函数式编程,有一原则就是无side effect,即不允许在函数内部改变外部变量的值。
3. 方法引用
方法引用是java8提供的进一步简化代码的方法,先看个例子:
inventory.sort((a1,a2)->a1.getWeight().compareTo(a2.getWeight()));
=>
inventory.sort(comparing(Apple::getWeight));
方法引用是java8为我们提供的一个新的语法糖,来帮助我们简化我们的lamda代码。
能够使用方法引用的场景有如下三种:
- 静态方法
- 任意类型的实例方法
- 现有对象实例方法
第二点和第三点可能有点难以理解。第二点指的是lamda中参数的方法;第三点指的是lamda外部对象的实例方法。
方法引用中还有个特例,就是构造函数引用,即我们可以通过方法引用的方式来调用某个类的构造函数,比如:
Supplier<Apple> s=()->new Apple();
=>
Supplier<Apple>s=Apple::new;