Lambda表达式,维基百科上的解释是一种用于表示匿名函数和闭包的运算符
Lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的匿名函数。
1 Lambda详细介绍
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型
1.1 Lambda表达式的语法
(parameters) -> expression 或(请注意语句的花括号) (parameters) -> { statements; }
示例:利用Lambda表达式定义一个Comparator对象 (javalambda_1)
1 |
|
Lambda表达式的组成:
- 参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。
- 箭头——箭头->把参数列表与Lambda主体分隔开。
- Lambda主体——比较两个Apple的重量。表达式就是Lambda的返回值了。
测试Lambda语法:
1 | 1. () -> {} |
2 函数式接口
在函数式编程语言中,Lambda表达式的类型是函数。**而在Java中,Lambda表达式是对象,它们必须依附于一类特别的对象类型——函数式接口(Functional Interface)**。
定义:如果一个接口中,有且只有一个抽象的方法(Object类中的方法不包括在内),那这个接口就可以被看做是函数式接口。
示例(Runnable接口):
1 |
|
来看下Runnable接口的声明,在Java8后,Runnable接口多了一个FunctionalInterface注解,表示该接口是一个函数式接口。但是如果我们不添加FunctionalInterface注解的话,如果接口中有且只有一个抽象方法时,编译器也会把该接口当做函数式接口看待。
注: 使用Lambda表达式可以创建函数式接口的实例。即Lambda表达式返回的是函数式接口类型
2.1 函数接口说明
函数接口可以(可以而不是必须)采用@FunctionalInterface 注解,
可以包涵以下成员:
- 用default 定义的默认方法,默认方法不是抽象的,其包涵一个默认实现。(也就是有方法体)
- 用static 定义的静态方法,静态方法是一个已经实现了的方法,不是抽象方法。
- Object类自带的方法,如toString,equals等方法。
必须包含以下
1 | 必须有一个抽象方法。因为函数接口本质是接口,所以不需要abstract修饰。 |
测试函数式接口语法:(javalambda_3)
1 | public interface Adder{ |
2.2 常用函数式接口介绍和使用
java.util.function 包 (javalambda_4)
Predicate (谓词, 断言)
Predicate
接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T s: list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
public static void main(String[] args) {
List<String> listOfStrings = Arrays.asList("nihao", "hello", "world", "welcome");
Predicate<String> nonEmptyStringPredicate = (String s) -> s.contains("h");
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
// nonEmpty.stream().forEach(System.out::println);
}Consumer (消费)
Consumer
定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void) 1
2
3
4
5
6
7
8
public static void main(String[] args) {
List<String> listOfStrings = Arrays.asList("nihao", "hello", "world", "welcome");
Consumer consumer = (item)-> System.out.println(item);
listOfStrings.stream().forEach(consumer);
}Function(函数)
Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
1
2
3
4
5
6
7
8
9public static void main(String[] args) {
List<String> listOfStrings = Arrays.asList("nihao", "hello", "world", "welcome");
Function<String, String> function = (item) -> item + "qingsong";
Stream<String> stream = listOfStrings.stream().map(function);
Consumer consumer = (item)-> System.out.println(item);
stream.forEach(consumer);
}
2.3 原始类型特化
Java类型要么是引用类型(比如Byte、Integer、Object、List),要么是原始类型(比如int、double、byte、char)。但是泛型(比如Consumer
1 | IntPredicate evenNumbers = (int i) -> i % 2 == 0; |
2.4 函数式接口异常
1 | /* (javalambda_5) |
3 Lambda类型推断
示例代码:(javalambda_6)
1 | List<Integer> list = Arrays.asList(3,2,4,5,6,7); |
这里参数的类型程序可以根据上下文进行推断,但是并不是所有的类型都可以推断出来,此时就需要我们显示的声明参数类型,当只有一个参数时小括号可以省略。当只有一行代码时,外边的大括号可以省略
Collections类的sort()方法。
1 | public static <T> void sort(List<T> list, Comparator<? super T> c) { |
假设有这么一个Student类Student implements A, B, C ,因为他实现了A,B,C三个接口,所以我们可以按接口A, B或C中任何一个的特性去排序,但是我比较完之后最终返回的一定是List
public static <T> void sort(List<T> list, Comparator<? super T> c)
里的泛型Comparator<? super T> c
语义上更宽泛一些,但实际上比较的就是T类型本身。
下面这lambda表达式可以直接推断出参数item的类型为String。
1 | List<String> list = Arrays.asList("nihao", "hello", "world", "welcome"); |
推断不出类型参数的 示例
1 | Collections.sort(list, Comparator.comparing(item -> item.length()).reversed()); |
这里要注意的是,我们这里的比较器是多层的,comparingInt返回一个比较器在调用reversed再返回一个比较器,最终我们需要的比较器是reversed返回的。第一个例子的lambda表达式就是一个比较器,我们直接就传入,java编译器很容易就推断出这个比较器里的类型参数。而第二个例子的lambda表达式仅仅作为第一层比较器comparingInt的参数传进去,它离这个上下文已经很远了,所以它对上下文的感知是很弱很远的,不能感知到集合list里的元素类型是什么!它就只能推断成Object类型
我们可以验证一下,很简单,现在它是2层,lambda表达式在第一层,我们把reversed去掉,不在第一层了吗?按理java就能感知到它的类型了。
1 | Collections.sort(list, Comparator.comparing(item -> item.length())); |
的确如此,现在编译器就可以推断出类型参数了。说明java编译器它能感知离上下文最近的,第二个reversed离上下文最近,我们可以推断它返回的比较器的类型参数,没问题,但是comparingInt(ToIntFunction<? super T> keyExtractor)里的参数ToIntFunction<? super T> keyExtractor离上下文很远了,无法推断其参数的类型参数
4 lambda使用局部变量
局部变量
局部变量是存储在栈上的,而栈上的内容在当前线程执行完成之后就会被GC回收掉。
lambda表达式
lambda表达式最终被处理为一个额外的线程去执行。绝对不是上面提到的线程。如果上面的线程执行完了,而这个线程又使用到了上面提到的局部变量会出现错误。
使用局部变量限制说明:实例变量存在堆中,而局部变量是在栈上分配,Lambda 表达(匿名类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。
5 方法引用
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。
5.1 方法引用语法
使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面
示例:(javatypelambda_7)
1 | // lambda表达式 |
说明:Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法
5.2 构建方法引用的方式
方法引用的标准形式是:类名::方法名
。
类型 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某个类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
静态方法引用
String::valueOf 等价于lambda表达式 (s) -> String.valueOf(s)
1
2
3
4
5List<Integer> listArr = Arrays.asList(3,5,6,7,8,2,0);
// lambda 表示
listArr.stream().map(item -> String.valueOf(item));
// 静态方法引用表示
listArr.stream().map(String::valueOf);特定实例对象的方法引用
String::toString 等价于lambda表达式 (s) -> s.toString()
实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
1
2
3
4
5
6
7//特定实例对象的方法引用
List<String> listStr = Arrays.asList("a","d","t","h","m","S","G");
// lambda 表示
listStr.stream().map(item -> item.toString());
listStr.stream().map(String::toString);
// 特定实例对象的方法引用
listStr.stream().map(String::toUpperCase);任意对象(属于同一个类)的实例方法引用
这里引用的是字符串数组中任意一个对象的compareToIgnoreCase方法。
1
2
3
4
5
6//任意对象(属于同一个类)的实例方法引用
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia" };
// lambda 表示
Arrays.sort(stringArray, (item, item2) -> item.compareToIgnoreCase(item2));
//任意对象(属于同一个类)的实例方法引用
Arrays.sort(stringArray, String::compareToIgnoreCase);构造方法引用
组成语法格式:Class::new
String::new, 等价于lambda表达式 () -> new String()
1
2
3
4
5
6//构造方法引用
//lambda 表达式
Supplier<String> supplier = () -> new String();
//构造方法引用
Supplier<String> supplier2 = String::new;
String aa = supplier2.get();
6 java对象和lambda表达式的类型转换
1 | public static void main(String[] args) { |
可以把lambda表达式看作函数式接口对象传递,而不用考虑函数接口本身的类型