它是什么
术语定义
百度百科定义: “Lambda 表达式”(lambda expression)是一个==匿名函数==,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包
java中的实现方式
lambda表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是: 一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)。
Lambda表达式 vs 匿名类
既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析。一个关键的不同点就是关键字 this。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向包围lambda表达式的类。另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。使用了Java 7的 invokedynamic 字节码指令来动态绑定这个方法。
同类技术:
Python, Ruby, Lua
为什么会出现
- 简化代码
- 有些运算中的变量甚至是算法函数过程其实不是必须要定义出来,只是中间过渡一下
- 对之后stream接口集合的便利操作提供基础支持,lambda 表达式能简化集合上数据的多线程或者多核的处理,提供更快的集合处理速度
实例走一波
1. 线程创建
//lambda之前
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("我在努力跑");
}
}).start();
//lambda之后
new Thread(() -> System.out.println("我起跑更快")).start();
2. Predicate 条件函数式接口,并且可以多个条件过滤
Predicate startsWithJ = (n) -> n.startsWith("J");
Predicate fourLetterLong = (n) -> n.length() ==4;
names.stream().filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : "+ n));
3. stream 接口
-
3.1 map 操作
本例介绍最广为人知的函数式编程概念map。它允许你将对象进行转换。例如在本例中,我们将 costBeforeTax 列表的每个元素转换成为税后的值。我们将 x -> x*x lambda表达式传到 map() 方法,后者将其应用到流中的每一个元素。然后用 forEach() 将列表元素打印出来。
// 不使用lambda表达式为每个订单加上12%的税
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
System.out.println(price);
}
// 使用lambda表达式
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);
-
3.2 reduce操作
在上个例子中,可以看到map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个。Map和Reduce操作是函数式编程的核心操作,因为其功能,reduce 又被称为折叠操作。另外,reduce 并不是一个新的操作,你有可能已经在使用它。SQL中类似 sum()、avg() 或者 count() 的聚集函数,实际上就是 reduce 操作,因为它们接收多个值并返回一个值。流API定义的 reduceh() 函数可以接受lambda表达式,并对所有值进行合并。IntStream这样的类有类似 average()、count()、sum() 的内建方法来做 reduce 操作,也有mapToLong()、mapToDouble() 方法来做转换。这并不会限制你,你可以用内建方法,也可以自己定义。在这个Java 8的Map Reduce示例里,我们首先对所有价格应用 12% 的VAT,然后用 reduce() 方法计算总和。
// 为每个订单加上12%的税
// 老方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
total = total + price;
}
System.out.println("Total : " + total);
// 新方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);
4. 匿名方法改进之路(Lambda 和方法引用实战)
-
第1种:传递代码
Java 8的API已经为你提供了一个 List 可用的 sort 方法,那么如何把排序策略传递给 sort 方法呢?sort方法的签名是这样的:
void sort(Comparator<? super E> c)
它需要一个 Comparator 对象来比较两个Apple!这就是在Java中传递策略的方式:它们必须包裹在一个对象里。我们说 sort 的行为被参数化了:传递给它的排序策略不同,其行为也会 不同。
第一个解决方案可以是这样的:public class AppleComparator implements Comparator<Apple> { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } } apples.sort(new AppleComparator())
-
第2步:使用匿名类
可以使用匿名类来改进方案,而不是实现一个 Comparator 却只实例化一次:
apples.sort(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } });
-
第3步:使用 Lambda 表达式
接下来使用 Lambda 表达式来改进方案:
apples.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
Comparator 具有一个叫作 comparing 的静态辅助方法,它可以接受一个 Function 来提取 Comparable 键值,并生成一个 Comparator 对象,它可以像下面这样用(注意你现在传递的Lambda只有一 个参数:Lambda说明了如何从苹果中提取需要比较的键值):
apples.sort(Comparator.comparing(((Apple apple) -> apple.getWeight())));
-
第4步:使用方法引用
方法引用就是替代那些转发参数的 Lambda 表达式的语法糖。可以用方法引 用改进方案如下:
apples.sort(Comparator.comparing(Apple::getWeight));
结束语
如上所述,lambda表达式等新特性给我们编程实现带来了新的支持可能,更加方便的计算(尤其是集合)
Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回 类型,可能还有一个可以抛出的异常的列表。
Lambda表达式让你可以简洁地传递代码。
函数式接口就是仅仅声明了一个抽象方法的接口。
只有在接受函数式接口的地方才可以使用Lambda表达式。
Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T>和BinaryOperator<T>。
为了避免装箱操作,对Predicate<T>和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。
环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配 和清理)可以配合 Lambda 提高灵活性和可重用性。
Lambda 表达式所需要代表的类型称为目标类型。
方法引用让你重复使用现有的方法实现并直接传递它们。
Comparator、Predicate和Function等函数式接口都有几个可以用来结合 Lambda 表达式的默认方法。