Java8新特性(一):Stream

370 阅读18分钟

Stream

学习内容

什么是 Stream

Java8中,Collection新增了两个流方法,分别是Stream() 和 parallelStream(),Stream将要处理的元素集合看作一种流,在流的过程中,可以通过 Lambda 表达式对集合进行大批量数据操作,比如:筛选、排序、聚合等。

顺序流和并行流

stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处: 如果流中的数据量足够大,并行流可以加快处速度,除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流

为什么要使用 Stream

在Java8之前,通常用 普通循环、for 循环 或者 Iterator 迭代来重排序合并数据,或者通过重新定义 Collections.sorts的 Comparator 方法来实现,这两种方式对 大数量系统来说,效率不理想。Stream的聚合操作与数据库SQL 的聚合操作 sorted、filter、map 等类似。应用层可以高效的实现类似数据库SQL 的聚合操作了,而在数据库操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据的处理效率。

Stream 特性

  • Stream可以由数组或集合创建,对流的操作分为两种:
    • 中间操作,每次返回一个新的流,可以有多个。
    • 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
  • stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
  • stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
  • stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

共用实体类


/**
 * @Description: todo
 * @Author: jianweil
 * @date: 2020/12/7 14:17
 */
public class Person {
    private String name;  // 姓名
    private int salary; // 薪资
    private int age; // 年龄
    private String sex; //性别
    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;
    }



    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }
}

Collect使用

collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

  • 归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。

  • 统计(count/averaging)

    Collectors提供了一系列用于数据统计的静态方法:

    • 计数:count
    • 平均值:averagingInt、averagingLong、averagingDouble
    • 最值:maxBy、minBy
    • 求和:summingInt、summingLong、summingDouble
    • 统计以上所有:summarizingInt、summarizingLong、summarizingDouble
  • 分组(partitioningBy/groupingBy)

    • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
    • 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
  • 接合(joining)

    • joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
  • 归约(reducing)

    • Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
  • 排序(sorted)

    sorted,中间操作。有两种排序:

    • sorted():自然排序,流中元素需实现Comparable接口
    • sorted(Comparator com):Comparator排序器自定义排序
  • 提取/组合

    • 流也可以进行合并、去重、限制、跳过等操作
  1. 代码
/**
 * @Description: 因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。
*                toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法
 * @Author: jianweil
 * @date: 2020/12/8 9:31
 */
public class TestCollect {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
        List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
        Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());

        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 25, "female", "Washington"));
        personList.add(new Person("Anni", 7800, 24, "female", "New York"));

        Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
                .collect(Collectors.toMap(Person::getName, p -> p));
        System.out.println("list:" + list);
        System.out.println("toList:" + listNew);
        System.out.println("toSet:" + set);
        System.out.println("toMap:" + map);


        // 求总数
        Long count = personList.stream().collect(Collectors.counting());
        // 求平均工资
        Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
        // 求最高工资
        Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
        // 求工资之和
        Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
        // 一次性统计所有信息
        DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));

        System.out.println("员工总数:" + count);
        System.out.println("员工平均工资:" + average);
        System.out.println("最高工资:" + max.get());
        System.out.println("员工工资总和:" + sum);
        System.out.println("员工工资所有统计:" + collect);



        /**
         * 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
         *
         * 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
         **/

        // 将员工按薪资是否高于8000分组
        Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
        // 将员工按性别分组
        Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
        // 将员工先按性别分组,再按地区分组
        Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
        System.out.println("员工按薪资是否大于8000分组情况:" + part);
        System.out.println("员工按性别分组情况:" + group);
        System.out.println("员工按性别、地区:" + group2);

        /**
         * joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
         */
        String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
        System.out.println("所有员工的姓名:" + names);
        List<String> listJoining = Arrays.asList("A", "B", "C");
        String string = listJoining.stream().collect(Collectors.joining("-"));
        System.out.println("拼接后的字符串:" + string);

        /**
         * Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持
         */
        // 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
        personList.stream().map(person -> person.getSalary()).forEach(System.out::println);
        Integer sumCollect = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 7000)));
        System.out.println("员工扣税薪资总和:" + sumCollect);

        // stream的reduce
        Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
        System.out.println("员工薪资总和:" + sum2.get());


        /**
         * sorted,中间操作。有两种排序:
         *
         * sorted():自然排序,流中元素需实现Comparable接口
         *
         * sorted(Comparator com):Comparator排序器自定义排序
         */

        // 按工资增序排序
        List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
                .collect(Collectors.toList());
        // 按工资倒序排序
        List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
                .map(Person::getName).collect(Collectors.toList());
        // 先按工资再按年龄自然排序(从小到大)
        List<String> newList3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Comparator.comparing(p->p.getAge())))
                .map(Person::getName).collect(Collectors.toList());
        // 先按工资再按年龄自定义排序(从大到小)
        List<String> newList4 = personList.stream().sorted((p1, p2) -> {
            if (p1.getSalary() == p2.getSalary()) {
                return p2.getAge() - p1.getAge();
            } else {
                return p2.getSalary() - p1.getSalary();
            }
        }).map(Person::getName).collect(Collectors.toList());

        System.out.println("按工资自然排序:" + newList);
        System.out.println("按工资降序排序:" + newList2);
        System.out.println("先按工资再按年龄自然排序:" + newList3);
        System.out.println("先按工资再按年龄自定义降序排序:" + newList4);

        /**
         * 流也可以进行合并、去重、限制、跳过等操作。
         */
        String[] arr1 = { "a", "b", "c", "d" };
        String[] arr2 = { "d", "e", "f", "g" };

        Stream<String> stream1 = Stream.of(arr1);
        Stream<String> stream2 = Stream.of(arr2);
        // concat:合并两个流 distinct:去重
        List<String> newListDistinct = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
        // limit:限制从流中获得前n个数据
        List<Integer> collectLimit = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
        // skip:跳过前n个数据
        List<Integer> collectLimit2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());

        System.out.println("流合并:" + newListDistinct);
        System.out.println("limit:" + collectLimit);
        System.out.println("skip:" + collectLimit2);


    }
}
  1. 运行结果
list:[1, 6, 3, 4, 6, 7, 9, 6, 20]
toList:[6, 4, 6, 6, 20]
toSet:[4, 20, 6]
toMap:{Tom=com.ljw.stream.entity.Person@58ceff1}
员工总数:4
员工平均工资:7875.0
最高工资:8900
员工工资总和:31500
员工工资所有统计:DoubleSummaryStatistics{count=4, sum=31500.000000, min=7000.000000, average=7875.000000, max=8900.000000}
员工按薪资是否大于8000分组情况:{false=[com.ljw.stream.entity.Person@548b7f67, com.ljw.stream.entity.Person@7ac7a4e4, com.ljw.stream.entity.Person@6d78f375], true=[com.ljw.stream.entity.Person@58ceff1]}
员工按性别分组情况:{female=[com.ljw.stream.entity.Person@7ac7a4e4, com.ljw.stream.entity.Person@6d78f375], male=[com.ljw.stream.entity.Person@58ceff1, com.ljw.stream.entity.Person@548b7f67]}
员工按性别、地区:{female={New York=[com.ljw.stream.entity.Person@6d78f375], Washington=[com.ljw.stream.entity.Person@7ac7a4e4]}, male={New York=[com.ljw.stream.entity.Person@58ceff1], Washington=[com.ljw.stream.entity.Person@548b7f67]}}
所有员工的姓名:Tom,Jack,Lily,Anni
拼接后的字符串:A-B-C
8900
7000
7800
7800
员工扣税薪资总和:3500
员工薪资总和:31500
按工资自然排序:[Jack, Lily, Anni, Tom]
按工资降序排序:[Tom, Lily, Anni, Jack]
先按工资再按年龄自然排序:[Jack, Anni, Lily, Tom]
先按工资再按年龄自定义降序排序:[Tom, Lily, Anni, Jack]
流合并:[a, b, c, d, e, f, g]
limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip:[3, 5, 7, 9, 11]

遍历/查找/匹配(foreach/find/match)

  1. 原理图
  2. 代码
/**
 *
 * @Description: todo
 * @Author: jianweil
 * @date: 2020/12/7 14:05
 *
 */
 class TestForeachfindMatch {
    public static void main(String[] args)
    {
        List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
        // 遍历输出符合条件的元素
        list.stream().filter(x -> x > 6).forEach(System.out::println);
        //并行
        list.stream().parallel().filter(x -> x > 6).forEach(System.out::println);
        // 匹配第一个
        Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
        //匹配任意
        Optional<Integer> findAny2 = list.stream().filter(x -> x > 6).findAny();
        // 匹配任意(适用于并行流)
        Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
        // 是否包含符合特定条件的元素
        boolean isMatch = list.stream().anyMatch(x -> x >= 9);
        System.out.println("匹配第一个值:" + findFirst.get());
        System.out.println("findAny2匹配任意一个值:" + findAny2.get());
        System.out.println("匹配任意一个值:" + findAny.get());
        System.out.println("是否存在大于等于9的值:" + isMatch);
    }
}
  1. 运行结果
7
9
8
8
9
7
匹配第一个值:7
findAny2匹配任意一个值:7
匹配任意一个值:8
是否存在大于等于9的值:true

筛选(filter)

  1. 原理图 筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
  2. 代码
public class TestFilter {
    public static void main(String[] args) {
        //筛选出Integer集合中大于7的元素,并打印出来
        List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
        Stream<Integer> stream = list.stream();
        stream.filter(x -> x > 7).forEach(System.out::println);
        //筛选员工中工资高于8000的人,并形成新的集合。 形成新集合依赖collect(收集)
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 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"));

        List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).filter(x -> x.getAge()>=24)
                .map(Person::getName).collect(Collectors.toList());
        System.out.print("高于8000的员工姓名:" + fiterList);
    }
}

  1. 运行结果
8
9
高于8000的员工姓名:[Anni, Owen]

聚合(max/min/count)

  1. 原理图 类似mysql

  2. 代码

public class TestMax {
    public static void main(String[] args) {
        //获取String集合中最长的元素。
        List<String> list = Arrays.asList("adnm", "admmt","aab", "aaa", "xbangd", "weoujgsd");
        Optional<String> max = list.stream().max(Comparator.comparing(String::length));
        Optional<String> min = list.stream().min(Comparator.comparing(String::length));
        //weoujgsd
        System.out.println("最长的字符串:" + max.get());
        //aaa
        System.out.println("最短的字符串:" + min.get());


        //获取Integer集合中的最大值。
        List<Integer> list1 = Arrays.asList(7, 6, 9, 4, 11, 6);
        // 自然排序
        Optional<Integer> max1 = list1.stream().max(Integer::compareTo);
        // 自定义排序
        Optional<Integer> max2 = list1.stream().max(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        });
        System.out.println("自然排序的最大值:" + max1.get());
        System.out.println("自定义排序的最大值:" + max2.get());



        //获取员工工资最高的人。
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 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"));

        Optional<Person> maxSalary = personList.stream().max(Comparator.comparingInt(Person::getSalary));
        System.out.println("员工工资最大值:" + maxSalary.get().getSalary());


        //计算Integer集合中大于6的元素的个数
        List<Integer> list4 = Arrays.asList(7, 6, 4, 8, 2, 11, 9);

        long count = list4.stream().filter(x -> x >= 6).count();
        System.out.println("list中大于=6的元素个数:" + count);
    }
}

  1. 运行结果
最长的字符串:weoujgsd
最短的字符串:aab
自然排序的最大值:11
自定义排序的最大值:11
员工工资最大值:9500
list中大于=6的元素个数:5

映射(map/flatMap)

  1. 原理图 映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
  1. 代码
/**
 * @Description: 映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
 *                  map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
 *                  flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
 * @Author: jianweil
 * @date: 2020/12/7 14:54
 */
public class TestMapAndFlatMap {
    public static void main(String[] args) {
        //英文字符串数组的元素全部改为大写。整数数组每个元素+3。
        String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
        List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
        List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
        List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());
        System.out.println("每个元素大写:" + strList);
        System.out.println("每个元素+3:" + intListNew);


        //将员工的薪资全部增加1000。
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 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"));
        // 不改变原来personList员工集合的方式
        List<Person> personListNew = personList.stream().map(person -> {
            Person personNew = new Person(person.getName(), 0, 0, null, null);
            personNew.setSalary(person.getSalary() + 10000);
            return personNew;
        }).collect(Collectors.toList());
        System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
        System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());

        // 改变原来personList员工集合的方式
        List<Person> personListNew2 = personList.stream().map(person -> {
            person.setSalary(person.getSalary() + 10000);
            return person;
        }).collect(Collectors.toList());
        System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
        System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());


        //将两个字符数组合并成一个新的字符数组。
        List<String> list = Arrays.asList("m-k-l-a", "1-3-5-7");
        List<String> listNew = list.stream().flatMap(s -> {
            // 将每个元素转换成一个stream
            String[] split = s.split("-");
            Stream<String> s2 = Arrays.stream(split);
            return s2;
        }).collect(Collectors.toList());

        System.out.println("处理前的集合:" + list);
        System.out.println("处理后的集合:" + listNew);
    }
}

  1. 运行结果
每个元素大写:[ABCD, BCDD, DEFDE, FTR]
每个元素+3:[4, 6, 8, 10, 12, 14]
一次改动前:Tom-->8900
一次改动后:Tom-->18900
二次改动前:Tom-->18900
二次改动后:Tom-->18900
处理前的集合:[m-k-l-a, 1-3-5-7]
处理后的集合:[m, k, l, a, 1, 3, 5, 7]

归约(reduce)

  1. 原理图 归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

  2. reduce()各个重载函数区别

  • 1个参数

Optional reduce(BinaryOperator accumulator);

假设Stream中的元素a[0]/a[1]/a[2]…a[n - 1],它表达的计算含义,使用Java代码来表述如下: 也就是说,a[0]与a[1]进行二合运算,结果与a[2]做二合运算,一直到最后与a[n-1]做二合运算。

相当java逻辑代码

        /* 
        * T result = a[0];
        * for (int i = 1; i < n; i++) {
        * 	result = accumulator.apply(result, a[i]);
        * }
        * return result;
        */
  1. 代码
    Supplier<Stream<Integer>> s = ()-> Stream.of(1, 2, 3, 4, 5, 6);
        /**
         * 求和,也可以写成Lambda语法:
         * Integer sum = s.reduce((a, b) -> a + b).get();
         */
        Integer sumBinaryOperator = s.get().reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }).get();
        Integer lambdaSum = s.get().reduce((a, b) -> a + b).get();

        System.out.println("1个参数sum:"+sumBinaryOperator);
        System.out.println("1个参数lambdaSum:"+lambdaSum);
        /**
         * 求最大值,也可以写成Lambda语法:
         * Integer max = s.reduce((a, b) -> a >= b ? a : b).get();
         */
        Integer maxBinaryOperator = s.get().reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer >= integer2 ? integer : integer2;
            }
        }).get();
        Integer lambdaMax = s.get().reduce((a, b) -> a >= b ? a : b).get();
        System.out.println("1个参数max:"+maxBinaryOperator);
        System.out.println("1个参数lambdaSum:"+lambdaMax);
  1. 结果
1个参数sum:21
1个参数lambdaSum:21
1个参数max:6
1个参数lambdaSum:6
  • 2个参数

T reduce(T identity, BinaryOperator accumulator);

顺序流和并行流有区别 ,并行流每个流初始值为identity

相当java逻辑代码

       /**2个参数
         * identity 初始值 ,注意并行流每个初始值一样
         *
         * T result = identity;
         * for (int i = 0; i < n; i++) {
         * 	result = accumulator.apply(result, a[i]);
         * }
         * return result;
         */
  1. 代码
    Supplier<Stream<String>> str = ()->Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
        /**
         * 以下结果将会是: [value]testt1t2teeeeeaaaataaa
         * 也可以使用Lambda语法:
         * System.out.println(s.reduce("[value]", (s1, s2) -> s1.concat(s2)));
         */
        String s1 = str.get().reduce("[value]", new BinaryOperator<String>() {
            @Override
            public String apply(String s11, String s22) {
                return s11.concat(s22);
            }
        }).toString();
        System.out.println(s1);
        String s2 = str.get().reduce("[value]", (s11, s22) -> s11.concat(s22)).toString();
        System.out.println(s2);
        
        List<Integer> intList = Arrays.asList(1,2,3);
        //顺序流 结果106。
        Integer result2=intList.stream().reduce(100, Integer::sum);
        System.out.println(result2);
        //并行流 结果306
        Integer result3=intList.parallelStream().reduce(100, Integer::sum);
        System.out.println(result3);

  1. 结果
[value]testt1t2teeeeeaaaataaa
[value]testt1t2teeeeeaaaataaa
106
306

顺序流结果106;并行流结果306?为什么是306呢?

因为在并行计算的时候,每个线程的初始累加值都是100,最后3个线程加出来的结果就是306。并行计算和非并行计算的结果居然不一样,这肯定不是JDK的问题,我们再看一下JDK中对identity的说明:

identity必须是accumulator函数的一个identity,也就是说必须满足:对于所有的t,都必须满足 accumulator.apply(identity, t) ==t 所以这里我们传入100是不对的,因为sum(100+1)!= 1。

这里sum方法的identity只能是0。如果我们用0作为identity,则stream和parallelStream计算出的结果是一样的。这就是identity的真正意图。*

  • 3个参数

    <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner);

    和前面的方法不同的是,多了一个combiner,这个combiner用来合并多线程计算的结果。 同样的,identity需要满足combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t) 大家可能注意到了为什么accumulator的类型是BiFunction而combiner的类型是BinaryOperator?

    public interface BinaryOperator extends BiFunction<T,T,T>

    BinaryOperator是BiFunction的子接口。BiFunction中定义了要实现的apply方法。其实reduce底层方法的实现只用到了apply方法, 并没有用到接口中其他的方法,所以我猜测这里的不同只是为了简单的区分。

    第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,第三个参数实际上是不生效的。

相当于java逻辑代码


       /**3个参数
         * identity 初始值 ,注意并行流每个初始值一样
         *
       	 *	 U result = identity;
    	 *     for (T element : this stream)
    	 *         result = accumulator.apply(result, element)
    	 *     return result;
         */

  1. 代码
    System.out.println(Stream.of(1, 2, 3).parallel().reduce(4, new BiFunction<Integer, Integer, Integer>() {
                    @Override
                    public Integer apply(Integer integer, Integer integer2) {
                        return integer + integer2;
                    }
                }
                , new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer integer, Integer integer2) {
                        return integer * integer2;
                    }
                }));
        //reduce的这种写法可以与以下写法结果相等(但过程是不一样的,三个参数时会进行并行处理)
        System.out.println(Stream.of(1, 2, 3).map(n -> n + 4).reduce((s111, s222) -> s111 * s222));


        /**
         * 模拟Filter查找其中含有字母a的所有元素,打印结果将是aa ab ad
         * lambda语法:
         * s1.parallel().reduce(new ArrayList<String>(), (r, t) -> {if (predicate.test(t)) r.add(t);  return r; },
         (r1, r2) -> {System.out.println(r1==r2); return r2; }).stream().forEach(System.out::println);
         */
        System.out.println("s33");
        Stream<String> s33 = Stream.of("aa", "ab", "c", "ad");
        Predicate<String> predicate = t -> t.contains("a");
        s33.parallel().reduce(new ArrayList<String>(), new BiFunction<ArrayList<String>, String, ArrayList<String>>() {
                    @Override
                    public ArrayList<String> apply(ArrayList<String> strings, String s) {
                        if (predicate.test(s)) {
                            strings.add(s);
                        }

                        return strings;
                    }
                },
                new BinaryOperator<ArrayList<String>>() {
                    @Override
                    public ArrayList<String> apply(ArrayList<String> strings, ArrayList<String> strings2) {
                        System.out.println(strings == strings2);
                        return strings;
                    }
                }).stream().forEach(System.out::println);

        System.out.println("s333");
        Stream<String> s333 = Stream.of("aa", "ab", "c", "ad");
        //模拟Filter查找其中含有字母a的所有元素,由于使用了r1.addAll(r2),其打印结果将不会是预期的aa ab ad
        s333.parallel().reduce(new ArrayList<String>(), (r, t) -> {if (predicate.test(t)) r.add(t);  return r; },
                (r1, r2) -> {r1.addAll(r2); return r1; }).stream().forEach(System.out::println);
  1. 结果
 210
Optional[210]
s33
true
true
true
ad
ab
aa
s333
ad
aa
ad
aa
ab
ad
aa
ad
aa
ab
ad
aa
ad
aa
ab
ad
aa
ad
aa
ab
  1. 其他
public class TestReduce {
    public static void main(String[] args) {

        //求Integer集合的元素之和、乘积和最大值。
        List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4,12);
        //List<Integer> list = Arrays.asList(0,0);
        // 求和方式1
        Optional<Integer> sumXY = list.stream().reduce((x, y) -> x + y);
        // 求和方式2
        Optional<Integer> sumInteger = list.stream().reduce(Integer::sum);
        // 求和方式3
        Integer sum3 = list.stream().reduce(0, Integer::sum);

        // 求乘积
        Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
        // 求最大值方式1
        Optional<Integer> maxXY = list.stream().reduce((x, y) -> x > y ? x : y);
        // 求最大值写法2
        Integer maxInteger = list.stream().reduce(1, Integer::max);

        System.out.println("list求和:" + sumXY.get() + "," + sumInteger.get() + "," + sum3);
        System.out.println("list求积:" + product.get());
        System.out.println("list求最大值:" + maxXY.get() + "," + maxInteger);

        //获取Integer集合中的最大值。
        // 自然排序
        Optional<Integer> maxIntegerMax = list.stream().max(Integer::compareTo);
        System.out.println("自然排序的最大值:" + maxIntegerMax.get());




        //求所有员工的工资之和和最高工资
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 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"));

        // 求工资之和方式1:
        Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
        // 求工资之和方式2:
        Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),(sum1, sum2) -> sum1 + sum2);
        // 求工资之和方式3:
        Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);
        // 求工资之和
        Integer sumSalary4 = personList.stream().collect(Collectors.summingInt(Person::getSalary));


        // 求最高工资方式1:
        Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                Integer::max);
        // 求最高工资方式2:
        Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                (max1, max2) -> max1 > max2 ? max1 : max2);
        // 求最高工资方式3:
        Optional<Integer> maxSalary3 = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));

        System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3+" "+sumSalary4);
        System.out.println("最高工资:" + maxSalary + "," + maxSalary2+ ","+maxSalary3.get());

    }
}
  1. 运行结果
list求和:41,41,41
list求积:25344
list求最大值:12,12
自然排序的最大值:12
工资之和:49300,49300,49300 49300
最高工资:9500,9500,9500

源码下载