- lambda表达式
1.1 什么是lambda
以java为例,可以对一个java变量赋一个值,比如int a = 1,而对于一个方法,一块代码也是赋予给一个变量的,对于这块代码,或者说被赋给变量的函数,就是一个lambda表达式
1 | 复制代码//为变量赋值 |
1.2 java为什么要引入lambda
lambda是为函数式编程服务的
编程语言共性之——什么是函数式编程?
函数式编程是一种编程范式,也就是如何编写程序的方法论,主要思想是把运算过程尽量编写成一系列嵌套的函数调用,FP强调“everything is lambda”,并且强调在逻辑处理中不变性的重要性
OOP强调“everything is object”,以及object之间的消息传递。通过消息传递改变每个Object的内部状态,但是很多情况代码的编写实际上是用不到对象的,比如,对一组数据做加工,先查询,然后聚合,聚合后排序,再join,再排序,再聚合,再转换(map)得到最终的结果。这个过程,用FP的函数就很自然
1 | 复制代码result = func1(func2(func3...funcN(x)))) |
java为了在原先oop的思想上增加函数式编程的使用,在java8上增加了lambda函数的新特性
除此之外,lambda表达式的引入还使得代码更为简洁,可以避免生成过多的污染环境的无用实现类(下面说)
1.3 如何使用lambda表达式
lambda表达式的引入可以避免生成过多的污染环境的实现类;
lambda表达式可以被赋值给一个变量,那么这个变量的类型是什么?
在java中,所有的Lambda的类型都是一个接口,而Lambda表达式本身,需要是这个接口的实现,这个接口需要具备三个特征,具备这些特征的接口叫做函数式接口
函数式接口只有一个抽象方法
default方法为默认实现,不计入抽象方法
如果接口声明了一个覆盖java.lang.Object的全局方法之一的抽象方法,那么它不会计入接口的抽象方法数量中,因为接口的任何实现都将具有java.lang.Object或其他地方的实现
如何使用lambda表达式
比如Comparator接口就是一个函数式接口,所以他可以使用lambda表达式,在之前使用comparator对一个list排序是下面这样的
1 | 复制代码List<Integer> list = new ArrayList<>(); |
可以看到上面实际真正有用的是return o1 - o2,上面的代码使用lambda表达式写如下
1 | 复制代码Collections.sort(list, ((o1, o2) -> o1-o2)); |
Lambda 表达式的基础语法:Lambda 操作符->将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表;
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体;
1 | 复制代码语法格式一:无参数,无返回值 |
1.4 lambda表达式方法引用,构造器引用和数组引用
方法引用
若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用
1 | 复制代码对象的引用 :: 实例方法名 |
①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
1 | 复制代码//对象的引用 :: 实例方法名 |
构造器引用
对于person类,有两个构造器
1 | 复制代码class Person { |
现在有一个工厂接口用来生成person类
1 | 复制代码// Person 工厂 |
我们可以通过 :: 关键字来引用 Person 类的构造器,来代替手动去实现这个工厂接口:
1 | 复制代码// 直接引用 Person 构造器 |
Person::new 这段代码,能够直接引用 Person 类的构造器。然后 Java 编译器能够根据上下文选中正确的构造器去实现 PersonFactory.create 方法
2.1 什么是Stream
Java 8引入了全新的Stream API,这里的Stream和I/O流不同,Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作,Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返
1 | 复制代码List<String> myList = |
上面是Stream的简单实用,可以看出它也是函数式编程,更多的表达了业务逻辑
2.2 常用api
创建Stream
1. Arrays.stream()
当在日常编程中面对的是一个数组,可以使用Arrays.stream()方法来使用Stream
1 | 复制代码Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96}; |
2. Stream.of()
当面对数组时除了可以使用Arrays.stream()方法外,还可以使用Stream将需要的数组转成Stream。这个方法不但支持传入数组,将数组转成Stream,也支持传入多个参数,将参数最终转成Stream
1 | 复制代码Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96}; |
3. Collection.stream()
这个就是最常见的Stream了。因为Collection是Java中集合接口的父接口,Java中的集合都继承或实现了此接口。所以Java中的集合都可以使用此方法来创建一个Stream
1 | 复制代码List<Integer> numbers = new ArrayList<>(); |
4.filter
这是一个Stream的过滤转换,此方法会生成一个新的流,其中包含符合某个特定条件的所有元素,filter接受一个函数作为参数,该函数用Lambda表达式表示
1 | 复制代码List<Integer> integerList = Lists.newArrayList(); |
5.map
map方法指对一个流中的值进行某种形式的转换。需要传递给它一个转换的函数作为参数
1 | 复制代码List<Integer> integerList = Lists.newArrayList(); |
6.flatMap
将多个Stream连接成一个Stream,这时候不是用新值取代Stream的值,与map有所区别,这是重新生成一个Stream对象取而代之
1 | 复制代码List<String> words = new ArrayList<String>(); |
7.limit方法和skip方法
limit(n)方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)
skip(n)方法正好相反,它会丢弃掉前面的n个元素
用limit和skip方法一起使用就可以实现日常的分页功能:
1 | 复制代码List<Integer> pageList = myList.stream() |
8.distinct方法和sorted方法
distinct方法会根据原始流中的元素返回一个具有相同顺序、去除了重复元素的流,这个操作显然是需要记住之前读取的元素。
1 | 复制代码List<Integer> myTestList = Lists.newArrayList(); |
sorted方法是需要遍历整个流的,并在产生任何元素之前对它进行排序。因为有可能排序后集合的第一个元素会在未排序集合的最后一位。
1 | 复制代码List<Integer> myTestList = Lists.newArrayList(); |
9.Collect
collect在流中生成列表,map,等常用的数据结构
1 | 复制代码将一个流收集到一个List中,只需要这样写就可以。 |
10.聚合操作
聚合是指将流汇聚为一个值,以便在程序中使用。聚合方法都是终止操作,聚合方法包括sum,count,max,min
1 | 复制代码long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum(); |
1 | 复制代码findFirst方法返回非空集合中的第一个值,它通常与filter方法结合起来使用 |
11.分组
对具有相同特性的值进行分组是一个很常见的功能
1 | 复制代码将一个Room对象集合按照高度分组。 |
2.3 Stream流的处理顺序
Stream流的中间操作具有延迟性,当且仅当存在终端操作时,中间操作才会被执行
1 | 复制代码Stream.of("d2", "a2", "b1", "b3", "c") |
执行此代码段时,不会打印任何内容,对上面的代码添加 forEach终端操作,就有打印内容了
1 | 复制代码Stream.of("d2", "a2", "b1", "b3", "c") |
1 | 复制代码filter: d2 |
但是可以看到输出结果并不是先将所有filter操作的打印语句打印出来;事实上,输出的结果却是随着链条垂直移动的,比如说,当 Stream 开始处理 d2 元素时,它实际上会在执行完 filter 操作后,再执行 forEach 操作,接着才会处理第二个元素
原因是出于性能的考虑。这样设计可以减少对每个元素的实际操作数,比如下面操作
1 | 复制代码Stream.of("d2", "a2", "b1", "b3", "c") |
终端操作 anyMatch()表示任何一个元素以 A 为前缀,返回为 true,就停止循环。所以它会从 d2 开始匹配,接着循环到 a2 的时候,返回为 true ,于是停止循环。
由于数据流的链式调用是垂直执行的,map这里只需要执行两次。相对于水平执行来说,map会执行尽可能少的次数,而不是把所有元素都 map 转换一遍
1 | 复制代码stream --> filter --> map --> sorted --> collect |
2.4 并行流
和迭代器不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item;
Stream具有平行处理能力,处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作
1 | 复制代码//parallel方法可以将任意的串行流转换为一个并行流 |
1 | 复制代码List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); |
最后
大家看完有什么不懂的欢迎在下方留言讨论
本文转载自: 掘金