Java 8 Stream API学习总结

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

这一次为什么要系统性的总结一下 Java 8 Stream API 呢?说得简单点,我们先不论性能,我们就是为了 装x ,而且要让这个 x 装得再优秀一些,仅此而已!

Stream Tests

Stream基础知识

流程

创建流流的中间操作流的最终操作

创建流

我们需要把哪些元素放入流中,常见的api有:

1// 使用List创建流 2list.stream() 3 4// 使用一个或多个元素创建流 5Stream.of(T value) 6Stream.of(T... values) 7 8// 使用数组创建流 9Arrays.stream(T[] array) 10 11// 创建一个空流 12Stream.empty() 13 14// 两个流合并 15Stream.concat(Stream<? extends T> a, Stream<? extends T> b) 16 17// 无序无限流 18Stream.generate(Supplier<T> s) 19 20// 通过迭代产生无限流 21Stream.iterate(final T seed, final UnaryOperator<T> f)

流的中间操作

1// 元素过滤 2filter 3limit 4skip 5distinct 6 7// 映射 8map 9flatmap 10 11// 排序

流的最终操作

通过流对元素的最终操作,我们想得到一个什么样的结果

构造测试数据

员工实体类

1/** 2 * 员工实体类 3 * @author Erwin Feng 4 * @since 2020/4/27 2:10 5 */ 6public class Employee { 7 8 /** 员工ID */ 9 private Integer id; 10 11 /** 员工姓名 */ 12 private String name; 13 14 /** 员工薪资 */ 15 private Double salary; 16 17 /** 构造方法、getter and setter、toString */ 18}

测试数据列表

1[ 2 { 3 "id":1, 4 "name":"Jacob", 5 "salary":1000 6 }, 7 { 8 "id":2, 9 "name":"Sophia", 10 "salary":2000 11 }, 12 { 13 "id":3, 14 "name":"Rose", 15 "salary":3000 16 }, 17 { 18 "id":4, 19 "name":"Lily", 20 "salary":4000 21 }, 22 { 23 "id":5, 24 "name":"Daisy", 25 "salary":5000 26 }, 27 { 28 "id":6, 29 "name":"Jane", 30 "salary":5000 31 }, 32 { 33 "id":7, 34 "name":"Jasmine", 35 "salary":6000 36 }, 37 { 38 "id":8, 39 "name":"Jack", 40 "salary":6000 41 }, 42 { 43 "id":9, 44 "name":"Poppy", 45 "salary":7000 46 } 47]

Stream API Test

filter 过滤

需求:查找薪酬为5000的员工列表

1List<Employee> employees = list.stream().filter(employee -> employee.getSalary() == 5000) 2 .peek(System.out::println) 3 .collect(Collectors.toList()); 4Assert.assertEquals(2, employees.size());

map 映射

需求:将薪酬大于5000的员工放到Leader对象中

1List<Leader> leaders = list.stream().filter(employee -> employee.getSalary() > 5000).map(employee -> { 2 Leader leader = new Leader(); 3 leader.setName(employee.getName()); 4 leader.setSalary(employee.getSalary()); 5 return leader; 6}).peek(System.out::println).collect(Collectors.toList()); 7Assert.assertEquals(3, leaders.size());

flatMap 水平映射

需求:将多维的列表转化为单维的列表

说明:

我们将薪酬在1000-3000的分为一个列表,4000-5000分为一个列表,6000-7000分为一个列表。

将这三个列表组合在一起形成一个多维列表。

1List<Employee> employees = multidimensionalList.stream().flatMap(Collection::stream).collect(Collectors.toList()); 2Assert.assertEquals(9, employees.size());

sorted 排序

需求:根据薪酬排序

1// 薪酬从小到大排序 2List<Employee> employees = list.stream().sorted(Comparator.comparing(Employee::getSalary)).peek(System.out::println).collect(Collectors.toList()); 3 4// 薪酬从大到小排序 5List<Employee> employees2 = list.stream().sorted(Comparator.comparing(Employee::getSalary).reversed()).peek(System.out::println).collect(Collectors.toList());

min 最小值

1double minValue = list.stream().mapToDouble(Employee::getSalary).min().orElse(0); 2Assert.assertEquals(1000, minValue, 0.0); 3 4Employee employee = list.stream().min(Comparator.comparing(Employee::getSalary)).orElse(null); 5assert employee != null; 6Assert.assertEquals(employee.getSalary(), minValue, 0.0);

max 最大值

1double maxValue = list.stream().mapToDouble(Employee::getSalary).max().orElse(0); 2Assert.assertEquals(7000, maxValue, 0.0);

average 平均值

1double sum = list.stream().mapToDouble(Employee::getSalary).sum(); 2double averageValue = list.stream().mapToDouble(Employee::getSalary).average().orElse(0); 3Assert.assertEquals(sum / list.size(), averageValue, 0.0);

match 匹配

1// allMatch 集合中的元素都要满足条件才会返回true 2// 薪酬都是大于等于1000的 3boolean isAllMatch = list.stream().allMatch(employee -> employee.getSalary() >= 1000); 4Assert.assertTrue(isAllMatch); 5 6// anyMatch 集合中只要有一个元素满足条件就会返回true 7// 有没有薪酬大于等于7000 8boolean isAnyMatch = list.stream().anyMatch(employee -> employee.getSalary() >= 7000); 9Assert.assertTrue(isAnyMatch); 10 11// noneMatch 集合中没有元素满足条件才会返回true 12// 没有薪酬小于1000的 13boolean isNoneMatch = list.stream().noneMatch(employee -> employee.getSalary() < 1000); 14Assert.assertTrue(isNoneMatch);

distinct 去重

默认的 distinct() 不接收参数,是根据 Object#equals(Object) 去重。根据API介绍,这是一个有中间状态的操作。

1List<Employee> employees = list.stream().distinct().collect(Collectors.toList()); 2Assert.assertEquals(9, employees.size());

如果我们要根据对象中的某个属性去重的,可以使用 StreamEx

1// 使用StreamEx去重 2List<Employee> employees2 = StreamEx.of(list).distinct(Employee::getSalary).collect(Collectors.toList()); 3Assert.assertEquals(7, employees2.size());

当然也可以使用JDK Stream API

1private static <T>Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { 2 Map<Object, Boolean> result = new ConcurrentHashMap<>(); 3 return t -> result.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; 4} 5 6List<Employee> employees3 = list.stream().filter(distinctByKey(Employee::getSalary)).collect(Collectors.toList()); 7Assert.assertEquals(7, employees3.size());

reduce 裁减

需求:计算薪酬总和

1// 先将员工列表转换为薪酬列表 2// 再计算薪酬总和 3double salarySum = list.stream().map(Employee::getSalary).reduce(Double::sum).orElse(0.0); 4double sum = list.stream().mapToDouble(Employee::getSalary).sum(); 5Assert.assertEquals(salarySum, sum, 0.0);

另外,我们也可以设定一个累加函数的标识值

1double salarySum5 = list.stream().map(Employee::getSalary).reduce(1.00, Double::sum); 2Assert.assertEquals(salarySum5, sum + 1, 0.0);

collector 流的终止结果

1// joining 拼接字符串 2String employeeNames = list.stream().map(Employee::getName).collect(Collectors.joining(", ")); 3System.out.println(employeeNames); // Jacob, Sophia, Rose, Lily, Daisy, Jane, Jasmine, Jack, Poppy 4 5// 返回一个List 6List<String> employeeNameList = list.stream().map(Employee::getName).collect(Collectors.toList()); 7System.out.println(employeeNameList); 8 9// 返回一个Set 10Set<String> employeeNameSet = list.stream().map(Employee::getName).collect(Collectors.toSet()); 11System.out.println(employeeNameSet); 12 13// 返回一个Vector 14Vector<String> employeeNameVector = list.stream().map(Employee::getName).collect(Collectors.toCollection(Vector::new)); 15System.out.println(employeeNameVector); 16 17// 返回一个Map 18Map<Integer, String> employeesMap = list.stream().collect(Collectors.toMap(Employee::getId, Employee::getName)); 19System.out.println(employeesMap);

count 统计

需求:薪酬为5000的员工数

不使用流

1int count2 = 0; 2for (Employee employee : list) { 3 if (employee.getSalary() == 5000) { 4 count2++; 5 } 6} 7System.out.println(count2);

使用流

1long count3 = list.stream().filter(employee -> employee.getSalary() == 5000).count(); 2Assert.assertEquals(count3, count2);

summarizingDouble 统计分析

1DoubleSummaryStatistics employeeSalaryStatistics = list.stream().collect(Collectors.summarizingDouble(Employee::getSalary)); 2System.out.println("employee salary statistics:" + employeeSalaryStatistics); 3 4DoubleSummaryStatistics employeeSalaryStatistics2 = list.stream().mapToDouble(Employee::getSalary).summaryStatistics(); 5System.out.println("employee salary statistics2:" + employeeSalaryStatistics2);

{count=9, sum=39000.000000, min=1000.000000, average=4333.333333, max=7000.000000}

partitioningBy 分区

分成满足条件(true)和不满足条件(false)两个区

需求:找出薪酬大于5000的员工

1Map<Boolean, List<Employee>> map = list.stream().collect(Collectors.partitioningBy(employee -> employee.getSalary() > 5000)); 2System.out.println("true:" + map.get(Boolean.TRUE)); 3System.out.println("false:" + map.get(Boolean.FALSE));

true:[Employee{id=7, name='Jasmine', salary=6000.0}, Employee{id=8, name='Jack', salary=6000.0}, Employee{id=9, name='Poppy', salary=7000.0}]

false:[Employee{id=1, name='Jacob', salary=1000.0}, Employee{id=2, name='Sophia', salary=2000.0}, Employee{id=3, name='Rose', salary=3000.0}, Employee{id=4, name='Lily', salary=4000.0}, Employee{id=5, name='Daisy', salary=5000.0}, Employee{id=6, name='Jane', salary=5000.0}]

groupingBy 分组

需求:根据员工薪酬分组

1Map<Double, List<Employee>> map = list.stream().collect(Collectors.groupingBy(Employee::getSalary)); 2System.out.println(map);

再举一个例子:薪酬 一> 总和(薪酬*员工数)

1Map<Double, Double> map3 = list.stream().collect(Collectors.groupingBy(Employee::getSalary, Collectors.summingDouble(Employee::getSalary))); 2System.out.println(map3);

parallel 平行计算

简单的说,就是启动多个线程计算

1private static void cal(Employee employee) { 2 try { 3 long sleepTime = employee.getSalary().longValue(); 4 TimeUnit.MILLISECONDS.sleep(sleepTime); 5 logger.info("employee name: {}", employee.getName()); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9} 10 11list.stream().parallel().forEach(StreamTest::cal);
12020-05-15 01:47:14.231 [ForkJoinPool.commonPool-worker-4] INFO com.fengwenyi.study_stream.StreamTest - employee name: Jacob 22020-05-15 01:47:15.226 [ForkJoinPool.commonPool-worker-2] INFO com.fengwenyi.study_stream.StreamTest - employee name: Sophia 32020-05-15 01:47:16.226 [ForkJoinPool.commonPool-worker-1] INFO com.fengwenyi.study_stream.StreamTest - employee name: Rose 42020-05-15 01:47:17.226 [ForkJoinPool.commonPool-worker-3] INFO com.fengwenyi.study_stream.StreamTest - employee name: Lily 52020-05-15 01:47:18.225 [main] INFO com.fengwenyi.study_stream.StreamTest - employee name: Jane 62020-05-15 01:47:18.228 [ForkJoinPool.commonPool-worker-7] INFO com.fengwenyi.study_stream.StreamTest - employee name: Daisy 72020-05-15 01:47:19.226 [ForkJoinPool.commonPool-worker-5] INFO com.fengwenyi.study_stream.StreamTest - employee name: Jack 82020-05-15 01:47:19.228 [ForkJoinPool.commonPool-worker-6] INFO com.fengwenyi.study_stream.StreamTest - employee name: Jasmine 92020-05-15 01:47:21.234 [ForkJoinPool.commonPool-worker-4] INFO com.fengwenyi.study_stream.StreamTest - employee name: Poppy

file 文件操作

1try (PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(Paths.get(tempFilePath)))) { // 使用 try 自动关闭流 2 list.forEach(printWriter::println); 3 list.forEach(employee -> printWriter.println(employee.getName())); // 将员工的姓名写到文件中 4} 5 6// 从文件中读取员工的姓名 7List<String> s = Files.lines(Paths.get(tempFilePath)).peek(System.out::println).collect(Collectors.toList());

测试代码

Study Java 8 Stream API

StreamTest Method List

学习链接

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Java日期时间API系列31

  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前

JDK1.8 之Stream API总结

Stream是Java8新增加的类,用来补充集合类。Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的。Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的数据的访问和有效管理(增删改),而Stream并没有提供访问和管理元素的方式,而是通过声明数据源的方式,利用可计算的操作在数据源上执行,当然