目录
1.Java8的Stream是什么?
2.流的构成:
3.Stream的使用
3.1 Stream的创建
3.2.1 Stream和parallelStream的简单区分:
3.3 Stream使用案例:
3.3.1 遍历匹配(foreach/find/match)
3.3.2 筛选(filter)
3.3.3聚合(max/min/count)
3.3.4 映射(map/flatMap)
3.3.5 规约(reduce)
3.3.6 收集 (collect)
3.3.7 提取组合(limit、skip、distince)
4.引用
1.Java8的Stream是什么?
Stream作为Java8的亮点之一,与java.io包里的InputStream和OutputStream的概念完全不同。Java8中的Stream是容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。StreamApi借助于Java8出现的Lambda表达式,极大提高编程效率和程序可读性。同时,他提供串行和并行俩种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常,编写并行代码很难而且容易出错,但是用StreamApi无需编写一行多线程代码,就可以很方便地写出高并发性能的代码。
Stream不是集合元素,它不是数据结构不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator用户只能显示地一个一个元素遍历并对其进行操作。高级版本的stream,用户只要给出需要对其包含的元素执行的操作即可,比如:“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器,单向不可往复,数据只能遍历一次,一次过后即用尽了,一去不返。
Stream不同于Iterator的是,可以并行操作,迭代器只能命令式地、串行化操作。而使用并行去遍历的时候,会将数据分成多段,其中每一个都在不同线程中处理,然后将结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/join框架(JSR166y)来拆分任务和加速处理过程。
Stream另外一大特点就是,数据源本身可以是无限的。
2.流的构成:
当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源->数据转换->执行操作获取想要的结果。每次转换原有的Stream对象不改变,返回一个新的Stream对象(可多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图:
3.Stream的使用
Stream
可以由数组或集合创建,对流的操作分为两种:
- 中间操作,每次返回一个新的流,可以有多个。
- 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
Stream的特性可归纳为:
- 不是数据结构;
- 它没有内部存储,它只是用操作管道从source(数据结构、数组、generator function、IO channel)抓取数据;
- 它也绝不修改自己所封装的底层数据结构的数据。例如Stream的filter操作会产生一个不包含被过滤元素的新Stream,而不是从source删除那些元素;
- 所有Stream的操作必须以lambda表达式为参数;
- 不支持索引访问;
- 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个;
- 很容易生成数组或者List;
- 惰性化;
- 很多Stream操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始;
- Intermediate操作永远是惰性化的;
- 并行能力;
- 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的;
- 可以是无限的。集合有固定大小,Stream 则不必。limit(n)和findFirst()这类的short-circuiting操作可以对无限的Stream进行运算并很快完成。
脑图总结:
3.1 Stream的创建
3.1.1Stream可通过集合、数组创建
// 1.通过java.util.Collection.stream()方法用集合创建流。
List<String> list = Arrays.asList("a","b","c");
//创建一个顺序流
Stream<String> stream = list.stream();
//创建一个并行流
Stream<String> parallelStream = list.parallelStream();
// 2.使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array = {1,2,3,4,5,6};
IntStream arrayTostream = Arrays.stream(array);
// 3.使用Stream的静态方法:of()、iterate()、generate()
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
integerStream.forEach(System.out::print);
System.out.println();
Stream<Integer> limitStream = Stream.iterate(0, (x) -> x + 3).limit(4);
limitStream.forEach(System.out::print);
System.out.println();
Stream<Double> generateStream = Stream.generate(Math::random).limit(3);
generateStream.forEach(System.out::println);
执行结果:
123456
0369
0.6411747078200662
0.9151646370590305
0.14447476425041816
3.2.1 Stream和parallelStream的简单区分:
stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇书,俩这处理方式不同:
如果流中数据足够大,可以使用并行流处理加快速度。
并行流除了可以直接创建,后还可以通过parallel()将顺序流转换为并行流:
OptionalInt first = arrayTostream.parallel().filter(s -> s > 2).findFirst();
3.3 Stream使用案例:
创建一个员工类来进行StreamAPI的操作
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 薪资
*/
private Double salary;
/**
* 性别
*/
private String sex;
/**
* 地区
*/
private String area;
public Employee() {
}
public Employee(String name, Integer age, Double salary, String sex, String area) {
this.name = name;
this.age = age;
this.salary = salary;
this.sex = sex;
this.area = area;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setArea(String area) {
this.area = area;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public Double getSalary() {
return salary;
}
public String getSex() {
return sex;
}
public String getArea() {
return area;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", sex='" + sex + '\'' +
", area='" + area + '\'' +
'}';
}
}
3.3.1 遍历匹配(foreach/find/match)
//3.1 遍历 匹配(foreach/find/match)
//Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单
List<Integer> integerList = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
System.out.println("要操作的元素集合:" + integerList);
System.out.println("遍历输出符合条件的元素,条件:大于6");
integerList.stream().filter(i -> i > 6).forEach(System.out::println);
Integer first = integerList.stream().filter(i -> i > 6).findFirst().get();
System.out.println("匹配第一个:" + first);
Integer anyInt = integerList.parallelStream().filter(i -> i > 6).findAny().get();
System.out.println("匹配任意(适用于并行流):" + anyInt);
System.out.println("是否包含符合特定条件的元素:");
boolean anyMathc = integerList.stream().anyMatch(x -> x > 6);
System.out.println("是否存在大于6的值?" + anyMathc);
输出结果:
要操作的元素集合:[7, 6, 9, 3, 8, 2, 1]
遍历输出符合条件的元素,条件:大于6
7
9
8
匹配第一个:7
匹配任意(适用于并行流):8
是否包含符合特定条件的元素:
是否存在大于6的值?true
3.3.2 筛选(filter)
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee("瑞克",19,10000.0,"男","北京"));
employeeList.add(new Employee("米琼恩",36,5673.2,"女","纽约"));
employeeList.add(new Employee("玛姬",22,7332.0,"女","上海"));
employeeList.add(new Employee("肖恩",38,10000.0,"男","华盛顿"));
employeeList.add(new Employee("莫尔",32,9000.0,"男","佛罗里达"));
employeeList.add(new Employee("格伦",23,7538.0,"男","首尔"));
employeeList.add(new Employee("卡尔",16,4234.0,"男","芝加哥"));
employeeList.add(new Employee("弩哥",50,12221.1,"男","拉斯维加斯"));
System.out.println("所有员工:" + employeeList);
List<String> rt8kNameList = employeeList.stream().filter(e -> e.getSalary() > 8000).map(Employee::getName).collect(Collectors.toList());
System.out.println("打印出工资超过8k的员工:" + rt8kNameList);
System.out.println();
结果打印:
所有员工:[Employee{name='瑞克', age=19, salary=10000.0, sex='男', area='北京'}, Employee{name='米琼恩', age=36, salary=5673.2, sex='女', area='纽约'}, Employee{name='玛姬', age=22, salary=7332.0, sex='女', area='上海'}, Employee{name='肖恩', age=38, salary=10000.0, sex='男', area='华盛顿'}, Employee{name='莫尔', age=32, salary=9000.0, sex='男', area='佛罗里达'}, Employee{name='格伦', age=23, salary=7538.0, sex='男', area='首尔'}, Employee{name='卡尔', age=16, salary=4234.0, sex='男', area='芝加哥'}, Employee{name='弩哥', age=50, salary=12221.1, sex='男', area='拉斯维加斯'}]
打印出工资超过8k的员工:[瑞克, 肖恩, 莫尔, 弩哥]
3.3.3聚合(max/min/count)
List<String> stringList = Arrays.asList("admin", "admn", "pig", "rabbit", "rocketmq", "as", "p");
String maxLength = stringList.stream().max(Comparator.comparing(String::length)).get();
System.out.println("最长的字符串:"+maxLength);
Optional<Integer> max = integerList.stream().max(Integer::compareTo);
System.out.println("integerList中最大的值:" + max.get());
Employee maxSalary = employeeList.stream().max(Comparator.comparingDouble(Employee::getSalary)).get();
System.out.println("最高的工资为:"+ maxSalary.getSalary());
long count = integerList.stream().filter(i -> i > 6).count();
System.out.println("integerList集合中大于6的个数:" + count);
结果:
最长的字符串:rocketmq
integerList中最大的值:9
最高的工资为:12221.1
integerList集合中大于6的个数:3
3.3.4 映射(map/flatMap)
可以将一个六种元素按照一定规则映射到另一个流中。分为map、platMap
map 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap:接受一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流连接成一个流
List<String> newStringList = stringList.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println("stringList元素都变成大写:" + newStringList);
List<Integer> newIntegerList = integerList.stream().map(x -> x + 3).collect(Collectors.toList());
System.out.println("integerList每个元素+3:" + newIntegerList);
//员工工资+1k
Map<String, Double> newSalaryMap = employeeList.stream().map(e -> {
Employee employee = new Employee(e.getName(), e.getAge(), e.getSalary() + 1000, e.getSex(), e.getArea());
return employee;
}).collect(Collectors.toMap(Employee::getName, Employee::getSalary));
System.out.println("员工工资+1000:" + newSalaryMap);
List<String> strings = Arrays.asList("a,v,c,e", "w,r,t");
List<String> newStrings = strings.stream().flatMap(s -> {
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("之前:" + strings);
System.out.println("之后:" + newStrings);
结果:
stringList元素都变成大写:[ADMIN, ADMN, PIG, RABBIT, ROCKETMQ, AS, P]
integerList每个元素+3:[10, 9, 12, 6, 11, 5, 4]
员工工资+1000:{米琼恩=6673.2, 瑞克=11000.0, 格伦=8538.0, 弩哥=13221.1, 卡尔=5234.0, 莫尔=10000.0, 肖恩=11000.0, 玛姬=8332.0}
之前:[a,v,c,e, w,r,t]
之后:[a, v, c, e, w, r, t]
3.3.5 规约(reduce)
能实现对集合求和,求乘积和求最值操作
//求和方式1
Optional<Integer> sum = integerList.stream().reduce((i1, i2) -> i1 + i2);
//求和方式2
Optional<Integer> sum2 = integerList.stream().reduce(Integer::sum);
//求和方式3
Integer sum3 = integerList.stream().reduce(0, Integer::sum);
//求乘积
Optional<Integer> reduce = integerList.stream().reduce((i1, i2) -> i1 * i2);
//求最大值
Optional<Integer> max1 = integerList.stream().reduce((i1, i2) -> i1 > i2 ? i1 : i2);
//求最大值方式2
Integer max2 = integerList.stream().reduce(1, Integer::max);
System.out.println("integerList求和:" + sum.get() + "," + sum2.get() + "," + sum3);
System.out.println("integerList乘积:" + reduce.get());
System.out.println("integerList最大值:" + max1.get()+ "," + max2);
//员工共工资之和1
Optional<Double> sSum1 = employeeList.stream().map(Employee::getSalary).reduce(Double::sum);
//员工共工资之和2
Double sSum2 = employeeList.stream().reduce(0.0, (ssum, e) -> ssum += e.getSalary(), (ssum1, ssum2) -> ssum1 + ssum2);
//员工共工资之和3
Double sSum3 = employeeList.stream().reduce(0.0, (sumSa, e) -> sumSa += e.getSalary(), Double::sum);
//员工最高工资1
Double maxSa1 = employeeList.stream().reduce(0.0, (maxSa, e) -> maxSa > e.getSalary() ? maxSa : e.getSalary(), Double::max);
//员工最高工资1
Double maxSa2 = employeeList.stream().reduce(0.0, (maxSa, e) -> maxSa > e.getSalary() ? maxSa : e.getSalary(), (m1, m2) -> m1 > m2 ? m1 : m2);
System.out.println("员工工资之和:" + sSum1.get() + "," + sSum2 + "," + sSum3);
System.out.println("员工最大工资:" + maxSa1 + "," + maxSa2);
结果:
integerList求和:36,36,36
integerList乘积:18144
integerList最大值:9,9
员工工资之和:65998.3,65998.3,65998.3
员工最大工资:12221.1,12221.1
3.3.6 收集 (collect)
collect,收集,可以说是内容最繁多,功能最丰富的部分了。从字面意思理解,就是把一个流收集起来,最终可以是收集成一个值,也可以是收集成一个新的集合。
collect主要依赖于java.util.stream.Collectors类内置的静态方法
Collectors提供了一系列用于数据统计的静态方法
计数:count
平均值:averagingInt、averagingLong、averagingDouble
最值:maxBy、minBy
求和:summingInt、summingLong、summingDouble
统计以上所有:summarizingInt、summarizingLong、summarizingDouble
Map<String, Integer> ageMap = employeeList.stream().filter(x -> x.getAge() % 2 == 0).collect(Collectors.toMap(Employee::getName, Employee::getAge));
System.out.println("年龄为奇数的员工集合:" + ageMap);
Set<Employee> set = employeeList.stream().filter(e -> e.getAge() % 2 == 0).collect(Collectors.toSet());
System.out.println("员工转为set集合:"+set);
Map<String, Double> gt8kSalaryMap = employeeList.stream().filter(e -> e.getSalary() > 8000).collect(Collectors.toMap(Employee::getName, Employee::getSalary, (e1, e2) -> e1));
System.out.println(gt8kSalaryMap);
//求员工总数
Long empCount = employeeList.stream().collect(Collectors.counting());
System.out.println("员工总数:"+empCount);
//求平均工资
Double avgSalary = employeeList.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println("平均工资:" + avgSalary);
//求最高工资
Double maxSalary1 = employeeList.stream().collect(Collectors.maxBy(Comparator.comparing(Employee::getSalary))).get().getSalary();
Optional<Double> maxSalary2 = employeeList.stream().map(Employee::getSalary).collect(Collectors.maxBy(Double::compare));
System.out.println("最高工资:" + maxSalary1 + "," + maxSalary2.get());
//求工资之和
Double sumSalary = employeeList.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println("工资之和:" + sumSalary);
//一次性统计所有信息
DoubleSummaryStatistics collect = employeeList.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("员工工资所有统计:" + collect);
年龄为奇数的员工集合:{米琼恩=36, 弩哥=50, 卡尔=16, 莫尔=32, 肖恩=38, 玛姬=22}
员工转为set集合:[Employee{name='肖恩', age=38, salary=10000.0, sex='男', area='华盛顿'}, Employee{name='玛姬', age=22, salary=7332.0, sex='女', area='上海'}, Employee{name='米琼恩', age=36, salary=5673.2, sex='女', area='纽约'}, Employee{name='卡尔', age=16, salary=4234.0, sex='男', area='芝加哥'}, Employee{name='弩哥', age=50, salary=12221.1, sex='男', area='拉斯维加斯'}, Employee{name='莫尔', age=32, salary=9000.0, sex='男', area='佛罗里达'}]
{瑞克=10000.0, 弩哥=12221.1, 莫尔=9000.0, 肖恩=10000.0}
员工总数:8
平均工资:8249.7875
最高工资:12221.1,12221.1
工资之和:65998.3
员工工资所有统计:DoubleSummaryStatistics{count=8, sum=65998.300000, min=4234.000000, average=8249.787500, max=12221.100000}
分组(partitioningBy/groupingBy)
分区:将stream按条件分为俩个Map,比如员工按照薪资是否高于8000分为2个部分
分组:将集合分为多个map,比如员工按照性别分组。有单级分组和多级分组。
Map<Boolean, List<Employee>> isGt8kMap = employeeList.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
System.out.println("员工是否工资大于8k分组后:" + isGt8kMap);
Map<String, List<Employee>> ageMap1 = employeeList.stream().collect(Collectors.groupingBy(Employee::getSex));
System.out.println("员工按照性别分组:" + ageMap);
Map<String, Map<String, List<Employee>>> sexAreaMap = employeeList.stream().collect(Collectors.groupingBy(Employee::getSex, Collectors.groupingBy(Employee::getArea)));
System.out.println("员工按照性别分组后再按照地区分组:" + sexAreaMap);
结果:
员工是否工资大于8k分组后:{false=[Employee{name='米琼恩', age=36, salary=5673.2, sex='女', area='纽约'}, Employee{name='玛姬', age=22, salary=7332.0, sex='女', area='上海'}, Employee{name='格伦', age=23, salary=7538.0, sex='男', area='首尔'}, Employee{name='卡尔', age=16, salary=4234.0, sex='男', area='芝加哥'}], true=[Employee{name='瑞克', age=19, salary=10000.0, sex='男', area='北京'}, Employee{name='肖恩', age=38, salary=10000.0, sex='男', area='华盛顿'}, Employee{name='莫尔', age=32, salary=9000.0, sex='男', area='佛罗里达'}, Employee{name='弩哥', age=50, salary=12221.1, sex='男', area='拉斯维加斯'}]}
员工按照性别分组:{米琼恩=36, 弩哥=50, 卡尔=16, 莫尔=32, 肖恩=38, 玛姬=22}
员工按照性别分组后再按照地区分组:{女={上海=[Employee{name='玛姬', age=22, salary=7332.0, sex='女', area='上海'}], 纽约=[Employee{name='米琼恩', age=36, salary=5673.2, sex='女', area='纽约'}]}, 男={拉斯维加斯=[Employee{name='弩哥', age=50, salary=12221.1, sex='男', area='拉斯维加斯'}], 华盛顿=[Employee{name='肖恩', age=38, salary=10000.0, sex='男', area='华盛顿'}], 芝加哥=[Employee{name='卡尔', age=16, salary=4234.0, sex='男', area='芝加哥'}], 首尔=[Employee{name='格伦', age=23, salary=7538.0, sex='男', area='首尔'}], 佛罗里达=[Employee{name='莫尔', age=32, salary=9000.0, sex='男', area='佛罗里达'}], 北京=[Employee{name='瑞克', age=19, salary=10000.0, sex='男', area='北京'}]}}
join接合 joining可以将stream中的元素用特定的连接符(没有的话直接连接)连接成一个字符串
String empNameList = employeeList.stream().map(Employee::getName).collect(Collectors.joining(","));
System.out.println("所有员工名字:" + empNameList);
String collect1 = stringList.stream().collect(Collectors.joining("-"));
System.out.println("stringList拼接后:" + collect1);
结果:
所有员工名字:瑞克,米琼恩,玛姬,肖恩,莫尔,格伦,卡尔,弩哥
stringList拼接后:admin-admn-pig-rabbit-rocketmq-as-p
Collectors.reduce()规约 相比于stream本身的reduce,增加了对自定义规约的支持
Double taxableSalarySum = employeeList.stream().collect(Collectors.reducing(0.0, Employee::getSalary, (i, j) -> (i + j - 5000)));
System.out.println("员工应扣税金额总和:"+ taxableSalarySum);
结果:
员工应扣税金额总和:25998.300000000003
排序(Sorted)
List<Employee> sortList = employeeList.stream().sorted(Comparator.comparing(Employee::getSalary)).collect(Collectors.toList());
System.out.println("员工工资升序排序:" + sortList);
List<Employee> reverseSortList = employeeList.stream().sorted(Comparator.comparing(Employee::getSalary).reversed()).collect(Collectors.toList());
System.out.println("员工工资降序排序:" + reverseSortList);
List<String> salaryAgeNameList = employeeList.stream().sorted(Comparator.comparing(Employee::getSalary).thenComparing(Employee::getAge)).map(Employee::getName).collect(Collectors.toList());
System.out.println("优先工资然后年龄升序排序:" + salaryAgeNameList);
结果:
员工工资升序排序:[Employee{name='卡尔', age=16, salary=4234.0, sex='男', area='芝加哥'}, Employee{name='米琼恩', age=36, salary=5673.2, sex='女', area='纽约'}, Employee{name='玛姬', age=22, salary=7332.0, sex='女', area='上海'}, Employee{name='格伦', age=23, salary=7538.0, sex='男', area='首尔'}, Employee{name='莫尔', age=32, salary=9000.0, sex='男', area='佛罗里达'}, Employee{name='瑞克', age=19, salary=10000.0, sex='男', area='北京'}, Employee{name='肖恩', age=38, salary=10000.0, sex='男', area='华盛顿'}, Employee{name='弩哥', age=50, salary=12221.1, sex='男', area='拉斯维加斯'}]
员工工资降序排序:[Employee{name='弩哥', age=50, salary=12221.1, sex='男', area='拉斯维加斯'}, Employee{name='瑞克', age=19, salary=10000.0, sex='男', area='北京'}, Employee{name='肖恩', age=38, salary=10000.0, sex='男', area='华盛顿'}, Employee{name='莫尔', age=32, salary=9000.0, sex='男', area='佛罗里达'}, Employee{name='格伦', age=23, salary=7538.0, sex='男', area='首尔'}, Employee{name='玛姬', age=22, salary=7332.0, sex='女', area='上海'}, Employee{name='米琼恩', age=36, salary=5673.2, sex='女', area='纽约'}, Employee{name='卡尔', age=16, salary=4234.0, sex='男', area='芝加哥'}]
优先工资然后年龄升序排序:[卡尔, 米琼恩, 玛姬, 格伦, 莫尔, 瑞克, 肖恩, 弩哥]
3.3.7 提取组合(limit、skip、distince)
String[] arr1 = {"a","b","c","d","e"};
String[] arr2 = {"e","d","h","i","j"};
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
System.out.println("Stream合并去重:" + newList);
List<Integer> newIntList = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
System.out.println("限制流从流中获取10个数字:" + newIntList);
List<Integer> skipNIntList = Stream.iterate(1, x -> x + 2).skip(4).limit(5).collect(Collectors.toList());
System.out.println("跳过前N个数据并且限制获取5个数字:" + skipNIntList);
结果:
Stream合并去重:[a, b, c, d, e, h, i, j]
限制流从流中获取10个数字:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
跳过前N个数据并且限制获取5个数字:[9, 11, 13, 15, 17]