Java Stream流

Java Stream流

Stream流

Stream流概述

Stream将要处理的元素集合看做一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等 Stream可以由数组、集合或者其静态方法of()、iterate()、generate()等创建,对流的操作分为两种:中间操作(可以有多个,每次都返回一个新的流)和终端操作(会产生一个新的集合或者值,每个流只能进行一次终端操作,终端操作结束后流无法再次使用) Stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果;Stream不会改变数据源,通常情况下会产生一个新的集合或者值;Stream具有延迟执行的特性,只有调用终端操作时,中间操作才会执行

Stream流创建

Stream可以由数组、集合或者其静态方法of()、iterate()、generate()等创建 通过java.util.Collection.stream()方法用集合创建Stream流

List<String> list = Arrays.asList("a","b","c","d","e","f","g");
//创建顺序流
Stream<String> stream = list.stream();
//创建并行流
Stream<String> parallelStream = list.parallelStream();

通过java.util.Arrays.stream(T[] array)方法用数组创建Stream流

int[] array = {1, 21, 10, 15, 4, 34};
IntStream intStream = Arrays.stream(array);

使用java.util.stream.Stream中的静态方法of()、iterate()、generate()等创建Stream流

Stream<Integer> ofStream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> iterateStream = Stream.iterate(0, x -> x + 3).limit(4);
Stream<Double> generateStream = Stream.generate(Math::random).limit(4);

Stream流和parallelStream流的区别:Stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流(本质上是基于Java7的Fork-Join框架实现的,Fork-Join是一个处理并行分解的高性能框架,其默认的线程数为宿主机的内核数),内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求

可以通过parallel()方法将顺序流转换成并行流

Stream<String> parallelStream2 = list.stream().parallel();

Stream流使用

基础数据:

public static List<Person> personList = new ArrayList<>();

static{
    personList.add(new Person("Tom", 8900, 23, "male", "New York"));
    personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
    personList.add(new Person("Lily", 7900, 21, "female", "Washington"));
    personList.add(new Person("Anni", 8200, 24, "female", "New York"));
    personList.add(new Person("Owen", 9500, 25, "male", "New York"));
    personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
}

@Data
static class Person{
    private String name;//姓名
    private int salary;//薪资
    private int age;//年龄
    private String sex;//性别male&female
    private String area;//地区
    
    public Person(String name, int salary, int age, String sex, String area) {
        this.name = name;
        this.salary = salary;
        this.age = age;
        this.sex = sex;
        this.area = area;
    }
}

终端操作(forEach迭代、find查找、match匹配)

Stream也是支持类似集合的遍历和匹配元素的,只不过Stream中的元素是以Optional类型存在的 forEach迭代:接收一个Function接口类型的变量 ** parallelStream中forEachOrdered和forEach的区别: ** forEachOrdered:按照集合元素的顺序输出 ** forEach:随机输出集合元素 find查找:findFirst、findAny **match匹配:anyMatch、allMatch、noneMatch

案例:遍历集合元素、查找第一个元素、查找任意元素、匹配指定条件的元素

List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
//遍历集合元素
list.stream().forEach(System.out::println);
//查找第一个元素
Optional<Integer> findFirst = list.stream().findFirst();
System.out.println("查找第一个元素:" + findFirst.get());
//查找任意元素(适用于并行流)
Optional<Integer> findAny = list.parallelStream().findAny();
System.out.println("查找任意元素:" + findAny.get());
//匹配指定条件的元素
boolean anyMatch = list.stream().anyMatch(i -> i < 6);
System.out.println("匹配指定条件的元素:" + anyMatch);

中间操作(filter筛选)

filter筛选:接收一个Predicate接口类型的变量,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作

案例一:筛选出Integer集合中大于7的元素并打印出来

List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
list.stream().filter(i -> i > 7).forEach(System.out::println);

案例二:筛选员工中工资高于8000的人并形成新的集合

List<Person> newPersonList = personList.stream().filter(p -> p.getSalary() > 8000).collect(Collectors.toList());
System.out.println("工资高于8000的人:" + newPersonList);  

终端操作(max最大值、min最小值、count计数)

案例一:获取String集合中最长、最短、最大和最小的元素

List<String> stringList = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
Optional<String> longString = stringList.stream().max(Comparator.comparing(String::length));
System.out.println("最长的字符串:" + longString.get());
Optional<String> shortString = stringList.stream().min(Comparator.comparing(String::length));
System.out.println("最短的字符串:" + shortString.get());
Optional<String> maxString = stringList.stream().max(String::compareTo);
System.out.println("最大字符串:" + maxString.get());
Optional<String> minString = stringList.stream().min(String::compareTo);
System.out.println("最小字符串:" + minString.get());

案例二:获取Integer集合中的最大值和最小值

List<Integer> integerList = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
Optional<Integer> maxInteger = integerList.stream().max(Integer::compare);
System.out.println("Integer集合中的最大值:" + maxInteger.get());
Optional<Integer> minInteger = integerList.stream().min((i1, i2) -> i1 < i2 ? i1 : i2);
System.out.println("Integer集合中的最小值:" + minInteger.get());

案例三:获取员工工资最高和最低的人

Optional<Person> maxPerson = personList.stream().max(Comparator.comparingInt(Person::getSalary));
System.out.println("员工工资最高者:" + maxPerson.get().toString());
Optional<Person> minPerson = personList.stream().min(Comparator.comparingInt(Person::getSalary));
System.out.println("员工工资最低者:" + minPerson.get().toString());

案例四:统计Integer集合中大于6的元素个数

long count = integerList.stream().filter(i -> i > 6).count();
System.out.println("Integer集合中大于6的元素个数:" + count);

中间操作(map/flatMap映射)

映射是将一个流中的元素按照一定的映射规则映射到另一个流中的操作 map:接收一个函数作为参数,该函数会被应用到每个元素上并将其映射成一个新的元素

flatMap:接收一个函数作为参数,将流中的每个值都变成另一个流,然后把所有的流连接成一个新的流

案例一:英文字符串数组的元素全部变成大写、Integer数组每个元素加3

String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
List<String> upperStrList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
System.out.println("元素全部变成大写:" + upperStrList);
List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
List<Integer> newIntList = intList.stream().map(i -> i + 3).collect(Collectors.toList());
System.out.println("每个元素加3:" + newIntList);

案例二:将每个员工的薪资增加1000元

List<Person> newPersonList = personList.stream().map(p -> {
    Person pp = new Person(p.getName(), 0, 0, null, null);
    pp.setSalary(p.getSalary() + 1000);
    return pp;
}).collect(Collectors.toList());
System.out.println("不改变原有集合处理:" + newPersonList);
List<Person> newPersonList2 = personList.stream().map(p -> {
    p.setSalary(p.getSalary() + 1000);
    return p;
}).collect(Collectors.toList());
System.out.println("改变原有集合处理:" + newPersonList2);

案例三:将两个字符数组合并成一个新的字符数组

List<String> list = Arrays.asList("a,b,c,d", "1,2,3,4");
System.out.println("处理前的集合:" + list);
List<String> newList = list.stream().flatMap(l -> {
    String[] temp = l.split(",");
    return Arrays.stream(temp);
}).collect(Collectors.toList());
System.out.println("处理后的集合:" + newList);

案例四:将两个字符数组合并成一个新的字符数组并去重

String[] words = new String[]{"Hello", "World"};
System.out.println("处理前的数组:" + Arrays.asList(words));
List<String> charList = Arrays.stream(words)
    .map(w -> w.split(""))
    .flatMap(Arrays::stream)
    .distinct()
    .collect(Collectors.toList());
System.out.println("处理后的集合:" + charList);

终端操作(reduce规约)

规约,也称缩减,是把一个流缩减成一个值,能实现对集合的求和、求乘积和最值操作

案例一:求Integer集合的元素之和、乘积、最大值和最小值

List<Integer> integerList = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
Optional<Integer> sum = integerList.stream().reduce((x, y) -> x + y);
Optional<Integer> sum2 = integerList.stream().reduce(Integer::sum);
Integer sum3 = integerList.stream().reduce(0, Integer::sum);
System.out.println("Integer集合的元素之和:" + sum.get() + "," + sum2.get() + "," + sum3);
Optional<Integer> multiply = integerList.stream().reduce((x, y) -> x * y);
System.out.println("Integer集合的元素乘积:" + multiply.get());
Optional<Integer> max = integerList.stream().reduce((x, y) -> x > y ? x : y);
Integer max2 = integerList.stream().reduce(0, Integer::max);
System.out.println("Integer集合的元素最大值:" + max.get() + "," + max2);
Optional<Integer> min = integerList.stream().reduce((x, y) -> x < y ? x : y);
System.out.println("Integer集合的元素最小值:" + min.get());

案例二:求所有员工的薪资之和、最高值和最低值

Optional<Integer> salarySum = personList.stream().map(p -> p.getSalary()).reduce(Integer::sum);
Integer salarySum2 = personList.stream().map(p -> p.getSalary()).reduce(0,Integer::sum);
Integer salarySum3 = personList.stream().reduce(0, (s, p) -> s += p.getSalary(), Integer::sum);
Integer salarySum4 = personList.stream().reduce(0, (s, p) -> s += p.getSalary(), (s1, s2) -> s1 + s2);
System.out.println("所有员工的薪资之和:" + salarySum.get() + "," + salarySum2 +"," + salarySum3 + "," + salarySum4);
Optional<Integer> maxSalary = personList.stream().map(p -> p.getSalary()).reduce(Integer::max);
Integer maxSalary2 = personList.stream().reduce(0, (m, p) -> m > p.getSalary() ? m : p.getSalary(), Integer::max);
Integer maxSalary3 = personList.stream().reduce(0, (m, p) -> m > p.getSalary() ? m : p.getSalary(), (m1, m2) -> m1 > m2 ? m1 : m2);
System.out.println("所有员工的最高薪资:" + maxSalary.get() + "," + maxSalary2 + "," + maxSalary3);
Optional<Integer> minSalary = personList.stream().map(p -> p.getSalary()).reduce(Integer::min);
Integer minSalary2 = personList.stream().reduce(0,(m, p) -> m < p.getSalary() ? m : p.getSalary(), Integer::min);
Integer minSalary3 = personList.stream().reduce(0, (m, p) -> m < p.getSalary() ? m : p.getSalary(), (m1, m2) -> m1 < m2 ? m1 : m2);
System.out.println("所有员工的最低薪资:" + minSalary.get() + "," + minSalary2 + "," + minSalary3);

终端操作(collect收集)

collect收集,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合,接收的参数是将流中的元素累积到汇总结果的各种方式,主要依赖java.util.stream.Collectors类中内置的静态方法

归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里 toMap()归集:当key重复时,会抛出异常java.lang.IllegalStateException: Duplicate key xxx,需要指定处理策略,比如覆盖之前的value。当value为null时,会抛出异常java.lang.NullPointerException,这是因为Collectors.toMap的底层实现是基于Map.merge方法来实现的,而merge方法中的value是不能为null的,如果为null,就会抛出空指针异常 案例:Integer集合元素归集、用户集合元素归集

//Integer集合元素归集
List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
List<Integer> newList = list.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
System.out.println("toList:" + newList);
Set<Integer> set = list.stream().filter(i -> i % 2 == 0).collect(Collectors.toSet());
System.out.println("toSet:" + set);
//用户集合元素归集
Map<String, Person> personMap = personList.stream().filter(p -> p.getSalary() > 8000).collect(Collectors.toMap(Person::getName, p -> p));
Map<String, Person> personMapNew = personList.stream().collect(Collectors.toMap(Person::getName, Function.identity()));
System.out.println("toMap:" + personMap +","+ personMapNew);
//当key重复时,会抛出异常java.lang.IllegalStateException: Duplicate key xxx
//Map<Integer, Person> personMap2 = personList.stream().collect(Collectors.toMap(Person::getAge, Function.identity()));
//需要指定处理策略,比如覆盖之前的value
Map<Integer, Person> personMap2 = personList.stream().collect(Collectors.toMap(Person::getAge, Function.identity(), (p1, p2) -> p2));
System.out.println("toMap2:" + personMap2);
//指定具体收集的Map
Map<Integer, String> personMap3 = personList.stream().collect(Collectors.toMap(Person::getAge, Person::getName, (p1, p2) -> p2, LinkedHashMap::new));
System.out.println("toMap3:" + personMap3);
//当value为null时,会抛出异常java.lang.NullPointerException,这是因为Collectors.toMap的底层实现是基于Map.merge方法来实现的,而merge方法中的value是不能为null的
//使用stream的collect重载方法来解决此问题
Map<Integer, String> personMap4 = personList.stream().collect(HashMap::new, (m, v) -> m.put(v.getAge(), v.getName()), HashMap::putAll);
System.out.println("toMap4:" + personMap4);
数据统计(counting/averaging/maxBy/minBy/summing/summarizing)

计数:counting 平均值:averagingInt、averagingLong、averagingDouble 最值:maxBy、minBy 求和:summingInt、summingLong、summingDouble 统计以上所有信息:summarizingInt、summarizingLong、summarizingDouble 案例:统计员工人数、平均工资、工资总额、最高工资和最低工资

//员工人数
long count = personList.stream().count();
Long count2 = personList.stream().collect(Collectors.counting());
System.out.println("员工人数:" + count +","+ count2);
//平均工资
Double averageSalary = personList.stream().collect(Collectors.averagingInt(Person::getSalary));
System.out.println("员工平均工资:" + averageSalary);
//工资总额
Integer sumSalary = personList.stream().collect(Collectors.summingInt(Person::getSalary));
System.out.println("员工工资总和:" + sumSalary);
//最高工资
Optional<Integer> maxSalary = personList.stream().map(p -> p.getSalary()).collect(Collectors.maxBy(Integer::compare));
Optional<Person> maxSalary2 = personList.stream().collect(Collectors.maxBy(Comparator.comparing(Person::getSalary)));
System.out.println("员工最高工资:" + maxSalary.get() +","+ maxSalary2.get().getSalary());
//最低工资
Optional<Integer> minSalary = personList.stream().map(p -> p.getSalary()).collect(Collectors.minBy(Integer::compare));
Optional<Person> minSalary2 = personList.stream().collect(Collectors.minBy(Comparator.comparing(Person::getSalary)));
System.out.println("员工最低工资:" + minSalary.get() +","+ minSalary2.get().getSalary());
//统计以上全部信息
IntSummaryStatistics statistics = personList.stream().collect(Collectors.summarizingInt(Person::getSalary));
System.out.println("员工工资所有统计信息:" + statistics);
分区(partitioningBy)和分组(groupingBy)

分区:将集合按照指定条件分为两个Map 分组:将集合分成多个Map,有单级分组和多级分组 Function.identity()返回一个输出和输入一样的lambda表达式对象,等价于形如(t -> t)形式的lambda表达式

案例一:将员工按薪资是否高于8000分为两部分

//将员工按薪资是否高于8000分为两部分
Map<Boolean, List<Person>> booleanMap = personList.stream().collect(Collectors.partitioningBy(p -> p.getSalary() > 8000));
System.out.println("员工按薪资是否大于8000分区:" + booleanMap);
//将员工按薪资是否高于8000分为两部分并统计人数
Map<Boolean, Long> booleanCountMap = personList.stream().collect(Collectors.partitioningBy(p -> p.getSalary() > 8000, Collectors.counting()));
System.out.println("员工按薪资是否大于8000分区并计数:" + booleanCountMap);

案例二:统计List集合重复元素出现的次数

List<String> stringList = Arrays.asList("apple", "apple", "banana", "apple", "orange", "banana", "papaya");
Map<String, Long> countMap = stringList.stream().collect(Collectors.groupingBy(s -> s, Collectors.counting()));
Map<String, Long> countMap2 = stringList.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Map<String, Long> countMap3 = stringList.stream().collect(Collectors.groupingBy(String::toString, Collectors.counting()));
System.out.println("List集合重复元素出现的次数:" + countMap + "," + countMap2 + "," + countMap3);

案例三:将员工按性别和地区分组并统计每个组的人数

//将员工按性别分组
Map<String, List<Person>> sexMap = personList.stream().collect(Collectors.groupingBy(Person::getSex));
System.out.println("员工按性别分组:" + sexMap);
//将员工按性别和地区分组
Map<String, Map<String, List<Person>>> sexAreaMap = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("员工按性别和地区分组:" + sexAreaMap);
//将员工按性别分组并统计每个组的人数
Map<String, Long> sexCountMap = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.counting()));
System.out.println("员工按性别分组并计数:" + sexCountMap);

案例四:统计相同姓名的员工总年龄大小

Map<String, Integer> sumAge = personList.stream().collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)));
System.out.println("统计相同姓名的员工总年龄大小:" + sumAge);

案例五:将员工按姓名分组并只保留员工的年龄

Map<String, List<Integer>> ageMap = personList.stream().collect(Collectors.groupingBy(Person::getName, Collectors.mapping(Person::getAge, Collectors.toList())));
System.out.println("将员工按姓名分组并只保留员工的年龄:" + ageMap);
接合(joining)

接合可以将集合中的元素用特定的连接符(没有的话,直接连接)连接成一个字符串儿 案例:将员工的姓名按逗号连接成一个字符串儿、将字符数组按中划线连接成一个字符串儿

//将员工的姓名按逗号连接成一个字符串儿
String nameStr = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
System.out.println("所有员工的姓名:" + nameStr);
//将字符数组按中划线连接成一个字符串儿
List<String> list = Arrays.asList("A", "B", "C");
String string = list.stream().collect(Collectors.joining("-"));
System.out.println("拼接后的字符串:" + string);
归约(reducing)

Collectors类提供的reducing方法相比于Stream类本身的reduce方法,增加了对自定义归约的支持 案例:每个员工的薪资减去个税起征点之后的薪资总和

//每个员工的薪资减去个税起征点之后的薪资总和
Integer salarySum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (s1, s2) -> s1 + s2 - 5000));
System.out.println("员工扣税薪资总和:" + salarySum);
Integer salarySum2 = personList.stream().collect(Collectors.reducing(0, Person::getSalary, Integer::sum));
Integer salarySum3 = personList.stream().map(p -> p.getSalary()).reduce(0, Integer::sum);
Integer salarySum4 = personList.stream().reduce(0, (s, p) -> s += p.getSalary(), Integer::sum);
Integer salarySum5 = personList.stream().collect(Collectors.summingInt(Person::getSalary));
System.out.println("员工薪资总和:" + salarySum2 +","+ salarySum3 +","+ salarySum4 +","+ salarySum5);
收集再处理(collectingAndThen)

该方法主要用于转换函数返回的类型 案例:员工集合list,根据员工年龄去除重复元素

List<Person> personListNew = personList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getAge))), ArrayList::new));
System.out.println("根据员工年龄去重:" + personListNew);

中间操作(sorted排序)

sorted排序:能够返回一个排过序的流对象的视图 sorted():自然排序,待排序的元素需实现Comparable接口 sorted(Comparator comparator):Comparator排序器自定义排序 案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序

//将员工按工资自然排序(由低到高)
List<Person> naturalSort = personList.stream().sorted(Comparator.comparing(Person::getSalary)).collect(Collectors.toList());
System.out.println("按工资自然排序:" + naturalSort);
//将员工按工资由高到低排序
List<Person> bigToSmallSort = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).collect(Collectors.toList());
System.out.println("按工资降序排序:" + bigToSmallSort);
//将员工按工资自然排序(工资一样则按年龄自然排序)(由低到高)
List<Person> naturalSort2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).collect(Collectors.toList());
System.out.println("先按工资再按年龄自然排序:" + naturalSort2);
//将员工按工资由高到低排序(工资一样则按年龄由大到小排序)
List<Person> bigToSmallSort2 = personList.stream().sorted((p1, p2) -> {
    if (p1.getSalary() == p2.getSalary()) {
        return p2.getAge() - p1.getAge();
    } else {
        return p2.getSalary() - p1.getSalary();
    }
}).collect(Collectors.toList());
System.out.println("先按工资再按年龄降序排序:" + bigToSmallSort2);

中间操作(concat合并、distinct去重、skip跳步、limit限制)

limit限制:用于返回前n个元素 skip跳步:用于舍弃前n个元素

案例:合并两个流并去除重复元素、迭代输出限制10个元素、迭代输出跳过一步

String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
Stream<String> stream1 = Arrays.stream(arr1);
Stream<String> stream2 = Arrays.stream(arr2);
List<String> concatDistinct = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
System.out.println("合并两个流并去除重复元素:" + concatDistinct);
List<Integer> limit = Stream.iterate(0, i -> i + 3).limit(10).collect(Collectors.toList());
System.out.println("迭代输出限制10个元素:" + limit);
List<Integer> skip = Stream.iterate(0, i -> i + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("迭代输出跳过一步:" + skip);

中间操作(peek窥视)

peek窥视:接收一个Consumer接口类型的变量,按照Consumer函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性,主要是用于调试,可以看到流中的数据经过每个处理点时的状态

//debug调试,查看流中的数据状态
Stream.of("one", "two", "three", "four")
    .filter(e -> e.length() > 3)
    .peek(e -> System.out.println("filter value:" + e))
    .map(String::toUpperCase)
    .peek(e -> System.out.println("map value:" + e))
    .collect(Collectors.toList());
//修改元素内部的属性
personList.stream()
    .peek(p -> p.setName(p.getName().toUpperCase()))
    .forEach(System.out::println);

peek()和map()的区别:peek()接收一个Consumer接口类型的变量,而map()接收一个Function接口类型的变量。Consumer接口没有返回值,它只是对Stream流中的元素进行某些操作,操作之后的数据并不返回到Stream流中,所以Stream流中的元素还是原来的元素,而Function接口有返回值,这意味着对于Stream流中的元素的所有操作都会作为新的结果返回到Stream流中

LICENSED UNDER CC BY-NC-SA 4.0