一、描述
Stream流提供了筛选与切片、映射、排序、匹配与查找、归约、收集等功能
筛选与切片:
filter:接收lambda,从流中排除某些元素
limit(n):截断流,使其元素不超过n
skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补
distinct:筛选,通过流所生成的元素的hashCode和equals去重
映射:
map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素
flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流
排序:
sorted()自然排序:comparable
sorted(Comparator com)定制排序:comparator
查找与匹配:
allMatch:检查是否匹配所有元素
anyMatch:检查是否至少匹配一个元素
noneMatch:检查是否所有都不匹配
findFirst:返回第一个元素
findAny:返回当前流中任意元素
count:返回当前流中元素个数
max:返回当前流中最大值
min:返回流中最小值
归约:
reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流
收集:
collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
二、Strem语法
Stream分为创建流、中间操作和终止操作三个部分
(1)创建流有三种方式:
第一种是使用通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象
//1、通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象 List list = new ArrayList(); Stream stream = list.stream(); Stream stream1 = list.parallelStream();
第二种是通过数组中的静态方法stream()获取数组流
//2、通过数组中的静态方法stream()获取数组流 String[] arr = new String[0]; Stream stream2 = Arrays.stream(arr);
第三种是通过Stream中的静态方法of()创建
List list = new ArrayList();//3、通过Stream中的静态方法of()创建 Stream stream3 = Stream.of(list);
除了上述三种,还有一种无限流的创建
下面的迭代表示从0开始,每次加2,无限循环;生成就会无限生成随机数
//无限流------迭代 Stream stream4 = Stream.iterate(0, x->x+2); //无限流------生成 Stream stream6 = Stream.generate(()->Math.random());
(2)中间操作
中间操作就是筛选切片、映射等都属于中间操作,操作后仍然返回一个流,以上面的无限流--迭代为例,可以使用截断流limit不让其无限循环,只要前10个
Stream stream5 = stream4.limit(10);
(3)终止操作,将流对象转换成数据对象的操作,以上述中间操作为例,加循环输出
//加中间操作的终止操作 stream4.limit(3).forEach(System.out::println);
输出结果:0 2 4
三、中间操作详解
Stream的操作主要就是中间操作如何处理,接下来详细说明中间操作
1、筛选与切片
为了代码演示需要,先创建一个Student类,和一个状态的枚举类
public enum Status{ FREE, BUSY, VOCATION } class Student{ private String name; private Integer age; private Status status; public Student(String name,Integer age){ this.name = name; this.age = age; } public Student(String name,Integer age, Status status){ this.name = name; this.age = age; this.status = status; } public String getName() { return name; } public Integer getAge() { return age; } public Status getStatus(){ return status; } public String toString(){ return JSON.toJSONString(this); } /*public int hashCode(){ int result = 1; result = this.getAge().hashCode() + this.getName().hashCode(); return result; } @Override public boolean equals(Object obj) { if(obj instanceof Student){ if(this.name.equals(((Student) obj).getName()) && this.age == ((Student) obj).getAge()){ return true; } }else { return super.equals(obj); } return false; }*/ }
创建一个Student的集合
List<Student> students1 = Arrays.asList( new Student("lcl1",18, Status.BUSY) ,new Student("lcl2",30, Status.VOCATION) ,new Student("lcl3",50, Status.FREE) ,new Student("lcl4",20, Status.FREE) ,new Student("lcl5",25, Status.BUSY) ,new Student("lcl6",8, Status.FREE) ,new Student("lcl2",30, Status.VOCATION));
筛选与切片:
filter:接收lambda,从流中排除某些元素
limit(n):截断流,使其元素不超过n
skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补
distinct:筛选,通过流所生成的元素的hashCode和equals去重
下面四个例子,分别对应四个中间操作的处理,操作内容分别为:
只获取年龄小于10的Student后,将集合中Student对象输出;期望输出对象为:lcl5
返回年龄大于10的前三个Student对象并输出:期望输出对象为:lcl1,lcl2,lcl3
跳过两个Student对象后取前三个:期望输出对象为:lcl3,lcl4,lcl5
输出集合中去重后的list对象:期望输出对象为集合中所有,包括两个lcl2,因为这里的去重操作是根据对象的hashCode和equals去判断是否重复的,由于两个lcl2都是new出来的,因此自然非同一个对象,但是如果我们重新hashCode和equals方法,按照名字和年龄一样,就会返回一样的hash码,且equals返回true(上述Student类中注释了重新的hashCode和equals方法,放开即可),则distinct会输出一个lcl2,下面演示输出按照重写hashCode和equals方法验证
/** * 筛选与切片 * filter:接收lambda,从流中排除某些元素 * limit(n):截断流,使其元素不超过n * skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补 * distinct:筛选,通过流所生成的元素的hashCode和equals去重 */ @Test public void tst2(){ log.info("filter测试=============================="); students1.stream().filter((x)->x.getAge() < 10).forEach(System.out::println); //惰性求值:所有中间操作不会做任何处理,只有在终止操作时,才会处理 //内部迭代,StreamAPI自己处理的迭代处理 //短路,只要找到满足的数据后,后面的流程就不再处理 log.info("limit测试=============================="); students1.stream().filter((x)->{log.info("filter");return x.getAge() > 10;}).limit(3).forEach(System.out::println); log.info("skip测试=============================="); students1.stream().skip(2).limit(3).forEach(System.out::println); log.info("distinct测试=============================="); students1.stream().distinct().forEach(System.out::println); }
测试结果:
2020-05-29 10:59:57.835 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : filter测试============================== {"age":8,"name":"lcl6","status":"FREE"} 2020-05-29 10:59:58.003 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : limit测试============================== 2020-05-29 10:59:58.005 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : filter {"age":18,"name":"lcl1","status":"BUSY"} 2020-05-29 10:59:58.005 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : filter {"age":30,"name":"lcl2","status":"VOCATION"} 2020-05-29 10:59:58.005 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : filter {"age":50,"name":"lcl3","status":"FREE"} 2020-05-29 10:59:58.005 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : skip测试============================== {"age":50,"name":"lcl3","status":"FREE"} {"age":20,"name":"lcl4","status":"FREE"} {"age":25,"name":"lcl5","status":"BUSY"} 2020-05-29 10:59:58.006 INFO 18008 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : distinct测试============================== {"age":18,"name":"lcl1","status":"BUSY"} {"age":30,"name":"lcl2","status":"VOCATION"} {"age":50,"name":"lcl3","status":"FREE"} {"age":20,"name":"lcl4","status":"FREE"} {"age":25,"name":"lcl5","status":"BUSY"} {"age":8,"name":"lcl6","status":"FREE"}
可以发现,所有的输出都与我们期望的一致,根据测试结果,有两个概念需要说下,就是惰性求值和短路,其实在代码的注释中已经有了,这里结合测试结果再详细说明一下
惰性求值:测试limit的时候,在中间操作中加了输出filter的操作,但是看测试结果,filter的输出和最终的结果输出一样,也是循环一次输出一次,而非通常想的会把所有的filter输出完之后,再循环输出测试结果,这就是惰性求值,即等输出结果时,在求值,而非过程中就求值
短路:还是以limit测试为例,在使用filter过滤后,应该还有6个元素符合条件,但是在limit(3)之后,只输出了3个对象,最重要的是,连filter字符串都是只输出了三个,而非6个,这就是因为Stream中间操作的短路操作,即找到满足自己的数据后,就不再处理,这样其实也可以提升效率。
2、映射:
map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素
flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流
为了做测试,先创建一个方法,传入一个字符串,返回一个单个字符的Stream流
public static Stream<Character> filterChaacter(String str){ List<Character> list = new ArrayList<>(); for (Character c : str.toCharArray()) { list.add(c); } return list.stream(); }
demo需求:
将数据组中的字符串变为大写
输出所有学生的名字
将数组中所有的字符串输出为单个字符
@Test public void test3(){ log.info("模拟map操作"); List<String> list = Arrays.asList("aaa","bbb","ccc","ddd"); list.stream().map((str)->str.toUpperCase()).forEach(System.out::println); students1.stream().map(Student::getName).forEach(System.out::println); log.info("模拟多重流操作"); Stream<Stream<Character>> stream = list.stream().map(Jdk8StreamDemoTest::filterChaacter); stream.forEach(sm -> sm.forEach(System.out::println)); log.info("模拟flatMap操作"); list.stream().flatMap(Jdk8StreamDemoTest::filterChaacter).forEach(System.out::println); }
上述代码中,模拟map操作块,即是对前两个demo需求的测试,没什么特别可说的,就是使用map从原有对象中获取我们所需要的对象;这里主要说明一下第三个demo需求,由于新增的filterChaacter方法返回的是一个Stream流,那么使用map的话就会得到一个Stream<Stream<Character>>的多重流,所以输出是,就需要双重循环(如模拟多重流代码块),非常的麻烦,因此就有了flatMap,他直接将返回的Stream流拼接成一个Stream流,这样输出的时候就只需要循环一次即可(如模拟flatMap操作)
测试结果
AAA BBB CCC DDD lcl1 lcl2 lcl3 lcl4 lcl5 lcl6 lcl2 2020-05-29 10:58:18.557 INFO 9444 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 模拟多重流操作 a a a b b b c c c d d d 2020-05-29 10:58:18.557 INFO 9444 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 模拟flatMap操作 a a a b b b c c c d d d
3、排序:
sorted()自然排序:comparable
sorted(Comparator com)定制排序:comparator
@Test public void test4(){ //自然排序 Arrays.asList("uio","fghf","dsf","ewre","gffjf","poiklk").stream().sorted().forEach(System.out::println); //定制排序 students1.stream().sorted((s1,s2)->s1.getAge().compareTo(s2.getAge())).forEach(System.out::println); //倒序排序 students1.stream().sorted((s1,s2)->-s1.getAge().compareTo(s2.getAge())).forEach(System.out::println); }
测试结果:
dsf ewre fghf gffjf poiklk uio {"age":8,"name":"lcl6","status":"FREE"} {"age":18,"name":"lcl1","status":"BUSY"} {"age":20,"name":"lcl4","status":"FREE"} {"age":25,"name":"lcl5","status":"BUSY"} {"age":30,"name":"lcl2","status":"VOCATION"} {"age":30,"name":"lcl2","status":"FREE"} {"age":50,"name":"lcl3","status":"FREE"} {"age":50,"name":"lcl3","status":"FREE"} {"age":30,"name":"lcl2","status":"VOCATION"} {"age":30,"name":"lcl2","status":"FREE"} {"age":25,"name":"lcl5","status":"BUSY"} {"age":20,"name":"lcl4","status":"FREE"} {"age":18,"name":"lcl1","status":"BUSY"} {"age":8,"name":"lcl6","status":"FREE"}
这个没什么特别可说的,就是一个排序,空参的方法使用自然排序,传入排序方法的,按照穿的比较器进行排序
4、查找与匹配:
allMatch:检查是否匹配所有元素
anyMatch:检查是否至少匹配一个元素
noneMatch:检查是否所有都不匹配
findFirst:返回第一个元素
findAny:返回当前流中任意元素
count:返回当前流中元素个数
max:返回当前流中最大值
min:返回流中最小值
demo需求:
判断集合中是否状态全为BUSY
判断集合中是否存在状态为BUSY的数据
判断集合中是否没有一个状态为BUSY的数据
按照年龄排序后取第一个
取任意一个元素
获取集合中数据总数
获取年龄最大的Student
获取年龄最小的Student
@Test public void test5(){ log.info("allMatch结果{}",students1.stream().allMatch((e)-> e.getStatus() == Status.BUSY)); log.info("anyMatch结果{}",students1.stream().anyMatch((e)-> e.getStatus() == Status.BUSY)); log.info("noneMatch结果{}",students1.stream().noneMatch((e)-> e.getStatus() == Status.BUSY)); Optional<Student> optionalStudent = students1.stream().sorted((e1, e2)->e1.getAge().compareTo(e2.getAge())).findFirst(); if(optionalStudent.isPresent()) log.info("findFirst结果{}",optionalStudent.get()); Optional<Student> optionalStudent1 = students1.stream().filter((e)->e.getStatus()==Status.FREE).findAny(); if(optionalStudent1.isPresent()) log.info("findAny结果{}",optionalStudent1.get()); log.info("count结果{}",students1.stream().count()); Optional<Student> optionalStudent2 = students1.stream().max((e1,e2)->e1.getAge().compareTo(e2.getAge())); if(optionalStudent2.isPresent()) log.info("max结果{}",optionalStudent2.get()); Optional<Student> optionalStudent3 = students1.stream().min((e1,e2)->e1.getAge().compareTo(e2.getAge())); if(optionalStudent3.isPresent()) log.info("min结果{}",optionalStudent3.get()); }
输出结果:
2020-05-29 10:56:39.830 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : allMatch结果false 2020-05-29 10:56:39.832 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : anyMatch结果true 2020-05-29 10:56:39.832 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : noneMatch结果false 2020-05-29 10:56:39.833 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : findFirst结果{"age":8,"name":"lcl6","status":"FREE"} 2020-05-29 10:56:39.936 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : findAny结果{"age":50,"name":"lcl3","status":"FREE"} 2020-05-29 10:56:40.025 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : count结果7 2020-05-29 10:56:40.027 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : max结果{"age":50,"name":"lcl3","status":"FREE"} 2020-05-29 10:56:40.028 INFO 18436 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : min结果{"age":8,"name":"lcl6","status":"FREE"}
上述代码示例中,有的地方的返回是个Optional,这个下一篇会说明
5、归约:
reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流
demo需求:
将一个集合中的数据,依次相加求和后再与100求和
将集合中学生的年龄求和后再加10
@Test public void test6(){ List<Integer> list = Arrays.asList(1,2,3,4,5); log.info("reduce结果{}",list.stream().reduce(100, (x,y)->x+y)); log.info("reduce计算年龄总和结果{}",students1.stream().map((x)->x.getAge()).reduce(10,(x,y)->x+y)); }
测试结果:
2020-05-29 10:56:08.448 INFO 17680 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : reduce结果115 2020-05-29 10:56:08.450 INFO 17680 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : reduce计算年龄总和结果191
6、收集:
collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,可以计算总和,最大值、最小值、平均值、分组、多重分组、分区和字符串操作等
由于collect的内容比较多,所以分开一个一个说明
(1)数据收集转换
demo:
将所有的名字获取,并添加到集合中
将所有的名字获取,并添加到特殊集合中,例如HashSet
List list = students1.stream().map(Student::getName).collect(Collectors.toList()); log.info("收集:collect测试结果{}",JSON.toJSONString(list)); Set set = students1.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new)); log.info("收集:特殊集合collect测试结果{}",JSON.toJSONString(set));
测试结果:
2020-05-29 10:44:37.478 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect测试结果["lcl1","lcl2","lcl3","lcl4","lcl5","lcl6","lcl2"] 2020-05-29 10:44:37.481 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:特殊集合collect测试结果["lcl5","lcl6","lcl3","lcl4","lcl1","lcl2"]
(2)计算总数、总和、最大值、最小值、平均值等数据收集操作
log.info("收集:collect总数测试结果{}",students1.stream().collect(Collectors.counting())); log.info("收集:collect计算年龄平均值测试结果{}",students1.stream().collect(Collectors.averagingInt(Student::getAge))); log.info("收集:collect计算年龄总和测试结果{}",students1.stream().collect(Collectors.summarizingInt(Student::getAge))); log.info("收集:collect获取年龄最大测试结果{}",students1.stream().collect(Collectors.maxBy((s1,s2)->s1.getAge().compareTo(s2.getAge()))).get()); log.info("收集:collect获取最小的年龄值测试结果{}",students1.stream().map(Student::getAge).collect(Collectors.minBy((s1,s2)->s1.compareTo(s2))).get());
测试结果:
2020-05-29 10:44:37.483 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect总数测试结果7 2020-05-29 10:44:37.484 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect计算年龄平均值测试结果25.857142857142858 2020-05-29 10:44:37.485 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect计算年龄总和测试结果IntSummaryStatistics{count=7, sum=181, min=8, average=25.857143, max=50} 2020-05-29 10:44:37.488 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect获取年龄最大测试结果{"age":50,"name":"lcl3","status":"FREE"} 2020-05-29 10:44:37.589 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect获取最小的年龄值测试结果8
(3)分组
需求:
单一分组:按照状态分组
多重分组:先按照状态分组,再按照年龄分组(以26为界,大于26为中老年,小于26为青少年)
Map map3 = students1.stream().collect(Collectors.groupingBy(Student::getStatus)); log.info("收集:collect按照Status分组测试结果{}", JSON.toJSONString(map3)); Map<Status,Map<String,List<Student>>> map = students1.stream().collect(Collectors.groupingBy(Student::getStatus ,Collectors.groupingBy((x)->{ if(((Student)x).getAge() > 26){ return "中老年"; }else{ return "青少年"; } }) )); log.info("收集:collect按照Status分组后按照年龄二次分组测试结果{}", JSON.toJSONString(map));
测试结果:
2020-05-29 10:44:37.591 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect按照Status分组测试结果{"VOCATION":[{"age":30,"name":"lcl2","status":"VOCATION"}],"FREE":[{"age":50,"name":"lcl3","status":"FREE"},{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"BUSY":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]} 2020-05-29 10:44:37.592 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect按照Status分组后按照年龄二次分组测试结果{"VOCATION":{"中老年":[{"age":30,"name":"lcl2","status":"VOCATION"}]},"FREE":{"中老年":[{"age":50,"name":"lcl3","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"青少年":[{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"}]},"BUSY":{"青少年":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}}
(4)使用IntSummaryStatistics计算总数、总和、最大值、最小值、平均值等操作
IntSummaryStatistics intSummaryStatistics = students1.stream().collect(Collectors.summarizingInt(Student::getAge)); log.info("收集:collect按照年龄平均值{},总数{},最大值{},最小值{},总和{}", intSummaryStatistics.getAverage(),intSummaryStatistics.getCount(),intSummaryStatistics.getMax(),intSummaryStatistics.getMin(),intSummaryStatistics.getSum());
测试结果:
2020-05-29 10:44:37.595 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect按照年龄平均值25.857142857142858,总数7,最大值50,最小值8,总和181
(5)字符串操作
String s = students1.stream().map(Student::getName).collect(Collectors.joining("==")); log.info("收集:collect连接字符串测试结果{}", s);
测试结果:
2020-05-29 10:44:37.595 INFO 19600 --- [ main] com.example.jdk8demo.Jdk8StreamDemoTest : 收集:collect连接字符串测试结果lcl1==lcl2==lcl3==lcl4==lcl5==lcl6==lcl2