目录
函数式编程思想
Lambda 表达式
Stream流
常用的Stream中的API
创建Stream
第一种是直接使用.Stream()的方式来创建
第二种采用.of()方式创建
具体来看看每一个API是怎么用的
concat
max
min
findFirst
findAny
count
peek
forEach
forEachOrdered
skip
distinct
map
flatMap
collection
toArray
reduce
函数式编程思想
在数学中函数就是有输入量 , 输出量的一套计算方案, 也就是拿什么东西做什么事情. 相对而言, 面向对象过分强调"必须通过对象的形式来做事情", 而函数式 思想则尽量忽略面向对象的复杂语法, 更加强调做什么,而不是以什么形式做
面向对象的思想 :
做一件事情, 找一个能解决这个事情的对象, 调用对象的方法, 完成事情.
函数式编程思想 :
只要能获取到结果, 谁去做的, 怎么做的都不重要. 重视的是结果, 不重视过程
Lambda 表达式
Lambda 表达式是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象,是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包。
最常见的一个例子就是新建线程,有时候为了省事,会用下面的方法创建并启动一个线程,这是匿名内部类的写法,new Thread
需要一个 implements 自Runnable
类型的对象实例作为参数,比较好的方式是创建一个新类,这个类 implements Runnable
,然后 new 出这个新类的实例作为参数传给 Thread。而匿名内部类不用找对象接收,直接当做参数。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中run的方法实现了");
}
}).start();
但是这样写的话是不是觉得很土,不简洁,所以这个时候Lambda表达式就派上了用场
new Thread(() -> {
System.out.println("新线程中run的方法实现了");
}).start();
小技巧: 可以使用alt+enter快速将匿名内部类和lambda之间相互转换
Lambda 表达式简化了匿名内部类的形式,可以达到同样的效果,但是 Lambda 要优雅的多。虽然最终达到的目的是一样的,但其实内部的实现原理却不相同。匿名内部类在编译之后会创建一个新的匿名内部类出来,而 Lambda 是调用 JVM invokedynamic
指令实现的,并不会产生新类。
Stream流
Stream流不只是Lambda表达式厉害,它其中还包含了很多 Java 8 中集合数据处理的利器,很多本来复杂、需要写很多代码的方法,比如过滤、分组等操作,往往使用 Stream 就可以在一行代码搞定,当然也因为 Stream 都是链式操作,一行代码可能会调用好几个方法。
Collection接口提供Stream()方法,让我们可以对集合使用Stream API来进行各种操作。要注意的是对同一个集合进行stream操作,执行的任何操作不会对源集合产生任何影响,所以对于同一个集合你可以采用多个stream来进行操作。
我们先来看看Stream流的接口定义,继承至BaseStream,所有的接口声明都是接收方法引用类型的参数,比如 filter
方法,接收了一个 Predicate
类型的参数,它就是一个函数式接口,常用来作为条件比较、筛选、过滤用,JPA
中也使用了这个函数式接口用来做查询条件拼接。
常用的Stream中的API
常用的API还是有一点多,可以稍微看看下面这张图
创建Stream
创建Stream的方式有很多,这里介绍两种常见的:
第一种是直接使用.Stream()的方式来创建
第二种采用.of()方式创建
可能你会疑惑为什么这么写,那最好的方式就是来看看源代码:
参数和返回值都是接受的一个泛型,所以无论你传入的什么都是可以的,需要注意的是, stream流创建后只能使用一次,创建了一个stream流使用两次会报 IllegalStateException异常。
具体来看看每一个API是怎么用的
concat
连接两个 Stream ,不改变其中任何一个 Steam 对象,返回一个新的 Stream 对象。
private static void concatStream(){
Stream<String> a = Stream.of("y","yy","yyy");
Stream<String> b = Stream.of("d","e");
Stream<String> c = Stream.concat(a,b);
}
max
一般用于求数字集合中的最大值,或者按实体中数字类型的属性比较,拥有最大值的那个实体。它接收一个 Comparator<T>
,上面也举到这个例子了,它是一个函数式接口类型,专门用作定义两个对象之间的比较,例如下面这个方法使用了 Integer::compareTo
这个方法引用。
public static void maxi(){
Stream<Integer> stream=Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 9);
Integer reMax=stream.max(Integer::compareTo).get();
System.out.println(reMax);
}
当然,我们也可以自己定制一个 Comparator
,顺便复习一下 Lambda 表达式形式的方法引用。
public static void maxi(){
Stream<Integer> stream=Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 9);
Comparator<Integer> comparator=(x,y)->(x.intValue()<y.intValue()?-1:(x.intValue()==y.intValue()?0:1));
Integer reMax=stream.max(comparator).get();
System.out.println(reMax);
}
min
与 max 用法一样,只不过是求最小值。
findFirst
获取 Stream 中的第一个元素。
findAny
获取 Stream 中的某个元素,如果是串行情况下,一般都会返回第一个元素,并行情况下就不一定了。
count
返回元素个数。
peek
建立一个通道,在这个通道中对 Stream 的每个元素执行对应的操作,对应 Consumer<T>
的函数式接口,这是一个消费者函数式接口,顾名思义,它是用来消费 Stream 元素的,比如下面这个方法,把每个元素转换成对应的大写字母并输出。
public static void testPeek(){
Stream<String> stream=Stream.of("a","b","c");
List<String> list=stream.peek(c-> System.out.println(c.toUpperCase())).collect(Collectors.toList());
// list.stream().forEach(System.out::println);
}
forEach
和 peek 方法类似,都接收一个消费者函数式接口,可以对每个元素进行对应的操作,但是和 peek 不同的是,forEach
执行之后,这个 Stream 就真的被消费掉了,之后这个 Stream 流就没有了,不可以再对它进行后续操作了,而 peek
操作完之后,还是一个可操作的 Stream 对象。
正好借着这个说一下,我们在使用 Stream API 的时候,都是一串链式操作,这是因为很多方法,比如接下来要说到的 filter
方法等,返回值还是这个 Stream 类型的,也就是被当前方法处理过的 Stream 对象,所以 Stream API 仍然可以使用。
forEachOrdered
功能与 forEach
是一样的,不同的是,forEachOrdered
是有顺序保证的,也就是对 Stream 中元素按插入时的顺序进行消费。为什么这么说呢,当开启并行的时候,forEach
和 forEachOrdered
的效果就不一样了。
skip
跳过前 n 条数据,例如下面代码,返回结果是 c。
distinct
元素去重,例如下面方法返回元素是 a、b、c,将重复的 b 只保留了一个。
map
map
方法的接口方法声明如下,接受一个 Function
函数式接口,把它翻译成映射最合适了,通过原始数据元素,映射出新的类型。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Function的声明也是这样的,接收一个T型的参数,返回一个R型的参数,这样就可以实现元素类型的转换,用于将当前类型转换为另一个合适的类型。
flatMap
当你的 Stream 是以下这几种结构的时候,需要用到 flatMap
方法,用于将原有二维结构扁平化。
Stream<String[]>
Stream<Set<String>>
Stream<List<String>>
以上这三类结构,通过 flatMap
方法,可以将结果转化为 Stream<String>
这种形式,方便之后的其他操作。
比如下面这个方法,将List<List<User>>
扁平处理,然后再使用 map
或其他方法进行操作。
public static void testFlatMap(){
List<String> list=Arrays.asList("a1","a2","b1","b2","c2");
List<String> list1=Arrays.asList("a","b","c");
List<List<String>> list2=new ArrayList<>();
list2.add(list);
list2.add(list1);
Stream<List<String>> stream=list2.stream();
List<String> stream1=stream.flatMap(Collection::stream).collect(Collectors.toList());
stream1.forEach(System.out::println);
}
collection
Collectors
为我们提供了很多拿来即用的收集器。比如我们经常用到Collectors.toList()
、Collectors.toSet()
、Collectors.toMap()
。另外还有比如Collectors.groupingBy()
用来分组,比如下面这个例子,按照 userId 字段分组,返回以 userId 为key,List 为value 的 Map,或者返回每个 key 的个数。
// 返回 userId:List<User>
Map<String,List<User>> map = user.stream().collect(Collectors.groupingBy(User::getUserId));
// 返回 userId:每组个数
Map<String,Long> map = user.stream().collect(Collectors.groupingBy(User::getUserId,Collectors.counting()));
toArray
collection
是返回列表、map 等,toArray
是返回数组,有两个重载,一个空参数,返回的是 Object[]
。
另一个接收一个 IntFunction<R>
类型参数。
reduce
它的作用是每次计算的时候都用到上一次的计算结果,比如求和操作,前两个数的和加上第三个数的和,再加上第四个数,一直加到最后一个数位置,最后返回结果,就是 reduce
的工作过程。
public static void testReduce(){
Stream<Integer> stream=Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 9);
Integer re=stream.reduce(1,(x,y)->x*y);
System.out.println(re);
}
当然还有几个map有关的函数也是比较重要的,这里就不做过多的介绍了~