参考
https://medium.com/javarevisited/you-dont-know-java-streams-in-practice-do-you-826e6aebba81
https://www.baeldung.com/java-8-streams
https://stackify.com/streams-guide-java-8/
《Java8 实战》
创建流
最常用的方法是直接使用 Collection 接口上的 stream 方法从一个容器中创建流:
List<Employee> employees = Arrays.asList(new Employee());
Stream<Employee> stream = employees.stream();
Stream<String> stream = Stream.of("Mary", "Tom", "", "Allen");
如果是数组的话,可以使用 Arrays.stream 方法:
int[] numbers = {2, 3, 5, 7, 11, 13};
IntStream sum = Arrays.stream(numbers).
使用 Stream 的 builder 方法创建流:
Stream.Builder<Employee> empStreamBuilder = Stream.builder();empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);Stream<Employee> empStream = empStreamBuilder.build();
中间操作(intermediate operation)
连接起来的操作称之为中间操作,就像自来水管,一节一节连接起来。中间操作接收一个流并返回另一个流。
先创建一个员工流
public List<Employee> employees = Arrays.asList(new Employee(1, "Tom", 4_000, "2001-02-13"),new Employee(2, "Jack", 2_000, "2001-02-13"),new Employee(3, "Jack", 2_000, "2001-02-13"),new Employee(4, "Jack 1", 2_000, "2024-02-13"),new Employee(5, "练习时长2年半", 2_000, "2011-02-13"),new Employee(6, "练习时长2年半", 2_000, "2001-02-13"),null
);
filter
过滤符合条件的元素。
// 过滤出 id 为 1 的员工
List<Employee> collect = employees.stream().filter(e -> e.getId() == 1).collect(Collectors.toList());
map
将元素映射成另一个元素。
List<String> collect = employees.stream().map(Employee::getName).collect(Collectors.toList());
flatMap
拍扁,接受一个将元素转为 Stream 的函数式接口,返回一个将所有元素转换成的流聚合之后的流。
List<String> words = Arrays.asList("aaa", "a", "bc");
List<String> uniqueCharacters = words.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
distinct
对流中的元素去重,使用流中的对象的 equals 方法来判断两个元素是否重复,所以记得按照自己的需要重写 equals 方法和 hashcode 方法。
List<Employee> collect = employees.stream().distinct().collect(Collectors.toList());
limit
和 SQL 中的 limit 关键字一样,limit 会返回一个不超过给定长度的流(限制只保留指定个数的元素)。
// 取流中第一个工资等于 1000 的员工
List<Employee> collect = employees.stream().filter(e -> Objects.nonNull(e) && e.getSalary() == 1_000).limit(1).collect(Collectors.toList());
skip
跳过流中指定个数的元素。
// 先过滤出工资等于 1000 的员工,再跳过前两个员工(从第 3 个员工开始取)
List<Employee> collect = employees.stream().filter(e -> Objects.nonNull(e) && e.getSalary() == 1_000).skip(2).collect(Collectors.toList());
sorted
按照指定的比较器排序流中的元素(如果不传比较器,那么流中的对象需要实现 Comparable 接口)。
// 按照员工的薪资排序
List<Employee> collect = employees.stream().sorted(Comparator.comparing(Employee::getSalary)).collect(Collectors.toList());
查找和匹配
allMatch、anyMatch、noneMatch、findFirst、findAny
findAny 不是为了随机选中,而是为了最快的选择出元素,如果 findAny 选择了 Null,会抛出空指针异常
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() == 1_000);
boolean noEmpIsNull = employees.stream().allMatch(Objects::nonNull);
boolean haveNoOneEmpNameIsTomCat = employees.stream().noneMatch(e -> "TomCat".equals(e.getName()));
Optional<Employee> firstEmp = employees.stream().findFirst();
Optional<Employee> anyEmp = employees.parallelStream().findAny();
终端操作(terminate operation)
终端操作就像是水龙头,它的后面不会再有任何的中间操作,终端操作是生成结果的步骤。
collect
将流中的元素收集起来,接收一个 Collector 接口的实现,最常用的就是传入一个将流中的元素采集到一个 List 中的采集器(上面的例子都在使用)。
groupingBy
将流中的元素按照指定的值作为分组依据将流中的元素分组。
// 将员工按照部门进行分组
Map<String, List<Employee>> groupByEmp = employees.stream().collect(Collectors.groupingBy(Employee::getDept));
reducing
将流中的元素合并为一个结果元素。
// 求所有员工的工资总和
Integer sumSalary = employees.stream().collect(Collectors.reducing(0, Employee::getSalary, Integer::sum));
joining
将流中的元素通过指定的分隔符拼接起来。
String allEmpName = employees.stream().map(Employee::getName).collect(Collectors.joining(", "));
summingInt 和 avging
summingInt 是求和,avging 是求平均值。
Integer sumSalary = employees.stream().collect(Collectors.summingInt(Employee::getSalary));
maxBy 和 minBy
maxBy 是求最大值,minBy 是求最小值。
Comparator<Employee> empCaloriesComparator = Comparator.comparingInt(Employee::getSalary);
Optional<Employee> salaryMostHighEmp = employees.stream().collect(Collectors.maxBy(empCaloriesComparator));
Optional<Employee> salaryMostLowEmp = employees.stream().collect(Collectors.minBy(empCaloriesComparator));
forEach
对流中的每一个元素执行指定的操作。
// 打印每一个员工
employees.stream().forEach(e -> {System.out.println(e.toString());
});
reduce
按照指定的规则将流中的元素合并为一个元素。
// 求所有员工薪水之和
Integer sumSalary = employees.stream().map(Employee::getSalary).reduce(0, Integer::sum);
count
和 SQL 中的 count 一样,统计流中的元素的个数。
// 统计员工数量
long empNm = employees.stream().filter(Objects::nonNull).count();
sum
和 SQL 中的 sum 一样,求流中的元素的和(需要先将流转化为数值类型的流)。
// 求流中所有元素的和
Integer sumSalary = employees.stream().mapToInt(Employee::getSalary).sum();
并行流
并行处理流中的数据。
employees.stream().parallel().forEach(e -> e.salaryIncrement(10.0));
Stream<Product> streamOfCollection = employees.parallelStream();
注意点
- 流只能消费一次
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
// java.lang.IllegalStateException:流已被操作或关闭
s.forEach(System.out::println);
- 流的中间操作是懒加载的
只有执行了终端操作才能触发处理,而且下一步不是需要等到上一步所有的数据都处理完了才开始执行当前步骤,整个处理过程是"流"的。