java JDK8新特性 - 3.Stream流

1.Stream流 - 概念

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

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

下面我们使用Stream API看看效果

public class Stream01 {

    public static void main(String[] args) {

        // 假设:我需要在一个整数集合中找出最小值
        int[] intArr = new int[] { 23, 15, 45, 91, 11, 25, 13, 99 };


        // 1.使用遍历方式:
        int min1 = Integer.MAX_VALUE;
        for (int i : intArr) {
            if (i < min1) {
                min1 = i;
            }
        }
        System.out.println("遍历方式:min1 = " + min1);


        // 2.使用stream流的方式:
        int min2 = IntStream.of(intArr).min().getAsInt();
        System.out.println("stream流的方式:min2 = " + min2);

        // stream流还提供多线程并发执行
        // 使用parallel()函数,就可以实现多线程并发执行
        int min3 = IntStream.of(intArr).parallel().min().getAsInt();
        System.out.println("stream流的方式(多线程并发):min3 = " + min3);


        /**
         * 结论:
         * 使用stream流能更简洁的实现对集合、数组的一些操作,提升开发效率
         *
         * 测试:用Stream流生成的集合,其元素 与 原集合的元素 是相等的,所以修改集合时会影响原集合!
         */
        List<Date> dateList = new ArrayList<Date>() {{ add(new Date(1546272000000L)); add(new Date(1577808000000L)); }};
        List<Date> dateList2 = dateList.stream().collect(Collectors.toList());
//        List<Date> dateList2 = dateList.stream().map(d -> d).collect(Collectors.toList());
        System.out.println("修改前:dateList = " + dateList);
        System.out.println("修改前:dateList2 = " + dateList2);
        dateList2.get(0).setTime(1514736000000L);
        System.out.println("修改后:dateList = " + dateList);
        System.out.println("修改后:dateList2 = " + dateList2);
        System.out.println("判断dateList[0] == dateList1[0],结果:" + (dateList.get(0) == dateList2.get(0)));
        // 打印:
        // 修改前:dateList = [Tue Jan 01 00:00:00 CST 2019, Wed Jan 01 00:00:00 CST 2020]
        // 修改前:dateList2 = [Tue Jan 01 00:00:00 CST 2019, Wed Jan 01 00:00:00 CST 2020]
        // 修改后:dateList = [Mon Jan 01 00:00:00 CST 2018, Wed Jan 01 00:00:00 CST 2020]
        // 修改后:dateList2 = [Mon Jan 01 00:00:00 CST 2018, Wed Jan 01 00:00:00 CST 2020]
        // 判断dateList[0] == dateList1[0],结果:true

    }

}

2.什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining(流水线[ˈpaɪplaɪnɪŋ]): 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

简单来说:
它是一个高级迭代器
它不是一个数据结构,不是集合,不存放数据
它只关注如何把数据高效的处理

我们来看一下外部迭代和内部迭代

public class Stream02 {

    public static void main(String[] args) throws InterruptedException {
        // 正常遍历数组:外部迭代
        int[] nums = { 1, 2, 3 };
        int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        System.out.println("nums数组求和:sum = " + sum);

        // 使用Stream内部迭代
        int sum2 = IntStream.of(nums).sum();
        System.out.println("nums数组求和:sum2 = " + sum2);

        /**
         * 使用Stream的好处:代码更简洁,Stream提供了一些高级功能,如:并行处理、惰性求值、短路
         *
         * Stream定义了2种操作:中间操作、终止操作
         * 见下面代码
         * map就是中间操作:返回Stream流的操作
         * sum就是终止操作:返回最终结果的操作
         */
        int sum3 = IntStream.of(nums).map(i -> i * 2).sum();
        System.out.println("nums数组求和乘2:sum3 = " + sum3);

        // 惰性求值就是没有调用终止操作,中间操作调用了也不会执行
        IntStream intStream = IntStream.of(nums).map(i -> {
            System.out.println("计算:i * 2 = " + (i * 2));
            return i * 2;
        });

        // 这里让线程阻塞5秒,这5秒内中间操作是不会计算的,只有调用终止操作,才会开始计算
        Thread.sleep(5000);

        int sum4 = intStream.sum();
        System.out.println("nums数组求和乘2:sum4 = " + sum4);
    }

}

3.流的创建

public class Stream03 {

    public static void main(String[] args) {
        /**
         * 流的完整操作是:流的创建 > 中间操作 > 终止操作
         *
         * 创建里Stream 流的四种方式
         */
        // 1.通过Collection得Stream()方法(串行流)或者parallelStream()方法(并行流)创建Stream
        List<String> list = Arrays.asList("1","2","3","4","0","222","33");
        Stream<String> listStream = list.stream();
        Stream<String> listStream1 = list.parallelStream();
        listStream.forEach(s -> System.out.println("通过Collection.stream()方法创建流:" + s));
        listStream1.forEach(s -> System.out.println("通过Collection.parallelStream()方法创建流:" + s));

        // 2.通过Arrays中得静态方法stream()创建数组流
        IntStream arrStream = Arrays.stream(new int[]{1,2,3});
        Stream<String> arrStream1 = Arrays.stream(new String[]{"A1", "B2"});
        Stream<Integer> arrStream2 = Arrays.stream(new Integer[]{1,2,3});
        arrStream.forEach(s -> System.out.println("通过Arrays.stream()静态方法创建流:" + s));
        arrStream1.forEach(s -> System.out.println("通过Arrays.stream()静态方法创建流:" + s));
        // 可以看见不能使用byte数组创建流,只能使用Byte创建流,Arrays.stream()只支持int、long、double的数组
//        Arrays.stream(new byte[]{1});
        Arrays.stream(new Byte[]{1});

        /**
         * 3.通过Stream类中得of()静态方法创建流
         * 该方法是重载方法,一个是单元素创建流,一个是元素数组创建流(使用了可变长参数)
         * 1.public static<T> Stream<T> of(T t) {
         * 2.public static<T> Stream<T> of(T... values)
         *
         * 从JDK1.5之后,java就提供了可变长参数(variable arguments,varargs)
         *
         * 这里看源码可以发现第2个of()方法中是调用Arrays.stream(values)来创建流的
         * 理论上讲Arrays.stream()创建流应该更快一些,但是网上有人测试居然是Stream.of()创建流更快
         * 文章《选择 Arrays.stream() 还是 Stream.of() ?》:https://www.oschina.net/question/3705394_2303025
         */
        Stream<String> stream = Stream.of("a","b","c");
        // 不能使用Stream.of()为基本类型数组创建流,它会认为这个数组是一个元素
        Stream<int[]> integerStream13 = Stream.of(new int[]{1, 2, 3});
        // 我们可以使用IntStream、LongStream、DoubleStream的of()静态方法为基本类型数组创建流
        IntStream intStream1 = IntStream.of(new int[]{1, 2, 3});
        // 创建11到20的数字流
        IntStream intStream2 = IntStream.rangeClosed(11, 20);
        stream.forEach(s -> System.out.println("通过Stream.of()静态方法创建流:" + s));
        intStream1.forEach(s -> System.out.println("通过IntStream.of()静态方法创建流:" + s));
        intStream2.forEach(s -> System.out.println("通过IntStream.rangeClosed()静态方法创建流:" + s));

        // 4.创建无限流(迭代、生成)
        // 生成(无限产生对象)(无限流一般要搭配短路操作,达到中断流的效果,limit()就是短路操作,限制元素数量)
        Stream<Double> stream1 = Stream.generate(() -> Math.random()).limit(10);
        stream1.forEach(s -> System.out.println("通过Stream.generate()静态方法创建流:" + s));

        // Random对象的ints()方法也可以创建无限的随机数流
        IntStream intStream3 = new Random().ints().limit(10);
        intStream3.forEach(s -> System.out.println("通过new Random().ints()静态方法创建流:" + s));

        // 迭代(需要传入一个种子,也就是起始值,然后传入一个一元操作)
        Stream<Integer> stream2 = Stream.iterate(1, (x) -> x * 2).limit(30);
        stream2.forEach(s -> System.out.println("通过Stream.iterate()静态方法创建流:" + s));
        IntStream intStream4 = IntStream.iterate(1, (x) -> x * 2).limit(30);
        int sum = intStream4.peek(s -> System.out.println("通过IntStream.iterate()静态方法创建流:" + s)).sum();
        System.out.println("第一天1块,第二天2块.....,30天一共需要:" + sum + "块钱");
        // 注:流一旦用过就不能再用了,再用会报错:
        // stream has already been operated upon or closed(流已经被操作或关闭)
//        int sum2 = intStream4.sum();
    }

}

4.中间操作(有状态 / 无状态)

public class Stream04 {

    public static void main(String[] args) {
        /**
         * 流的中间操作分为无状态操作和有状态操作
         * 无状态:这种操作跟其他元素的前后没有关系
         * 有状态:这种操作会依赖其他元素,如sorted排序,它需要依赖于流的所有元素都计算完毕,它再排序才准确
         *
         * 无状态操作:
         * map / mapToXxx(映射:替换流的元素,例:可以把字符串集合映射为整数集合)
         * flatMap / flatMapToXxx(平面映射:用一个流替换流的元素,例:假设班级对象中有学生集合,可以把班级集合映射为学生集合)
         * filter(过滤)
         * peek(遍历,peek本意是偷窥,就是在流还没结束时看一下里面的元素)
         * unordered(无序化,以提升并行流的性能)
         *
         * 有状态操作:
         * distinct(去重)
         * sorted(排序)
         * limit(限流)
         * skip(跳过)
         */
        String str = "my name is 007";

        // 使用map将单词集合 映射为 字符串长度集合
        List<Integer> list = Stream.of(str.split(" ")).map(String::length).collect(Collectors.toList());
        System.out.println("使用map将单词集合 映射为 字符串长度集合:" + list);

        // 使用filter过滤,保留长度>2的元素
        List<Integer> list2 = Stream.of(str.split(" ")).map(String::length).filter(i -> i > 2).collect(Collectors.toList());
        System.out.println("使用map将单词集合 映射为 字符串长度集合(使用filter过滤,保留长度>2的元素):" + list2);

        // 使用flatMap将单词集合 映射为 字母集合
        // IntStream、LongStream 并不是Stream的子类,需要调用boxed()进行装箱
        List<Integer> list3 = Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed()).collect(Collectors.toList());
        // 因为char其实是0~65535的数字,所以需要把数字再转成char,这里加一层map
        List<Character> list4 = Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed()).map(i -> (char) i.intValue()).collect(Collectors.toList());
        System.out.println("使用flatMap将单词集合 映射为 字母集合:" + list4);

        // peek(遍历,peek本意是偷窥,就是在流还没结束时看一下里面的元素)
        Stream.of(str.split(" ")).peek(s -> System.out.println("使用peek偷窥:" + s)).forEach(s -> System.out.println("使用forEach遍历:" + s));


        // limit 主要用于无限流
        List<Integer> list5 = Stream.iterate(1, (x) -> x * 2).limit(15).collect(Collectors.toList());
        System.out.println("创建一个无限流,限制15个元素:" + list5);
//        Stream.iterate(10, (x) -> x * 2).limit(30).forEach(System.out::println);

        // unordered()操作不会执行任何操作来显式地对流进行排序。它的作用是消除了流必须保持有序的约束,从而允许后续操作使用不必考虑排序的优化。
        // 您可以在Java 8文档中阅读此内容:对于顺序流,顺序的存在与否不会影响性能,只影响确定性。
        // 如果流是顺序的,则在相同的源上重复执行相同的流管道将产生相同的结果;如果是非顺序流,重复执行可能会产生不同的结果。
        // 对于并行流,放宽排序约束有时可以实现更高效的执行。
        // ...在流有序时, 但用户不特别关心该顺序的情况下,使用 unordered 明确地对流进行去除有序约束可以改善某些有状态或终端操作的并行性能。
        Stream.of(5, 1, 2, 6, 3, 7, 4).unordered().forEach(System.out::print);
        System.out.println();
        Stream.of(5, 1, 2, 6, 3, 7, 4).unordered().parallel().forEach(System.out::print);
        System.out.println();

        // skip 跳过(参数填1跳过1条,参数填0相当于不跳过元素,参数填负数会报错)
        List<String> list6 = Stream.of(str.split(" ")).skip(2).collect(Collectors.toList());
        System.out.println("获取单词集合,使用skip跳过前2个元素:" + list6);
    }

}

5.终止操作(短路 / 非短路)

public class Stream05 {

    public static void main(String[] args) {
        /**
         * 流的终止操作分为非短路操作和短路操作
         * 短路:不需要等整个流全部执行完毕,直接就可以返回,如findFirst(),流的第一条数据处理完毕就可以返回了,不需要等流所有操作都处理完
         * 非短路:和短路相反,需要等流全部执行完毕,才能返回
         *
         * 非短路操作:
         * forEach / forEachOrdered(遍历 / (并行流)按顺序遍历)
         * collect / toArray(集合收集器 / 数组收集器)
         * reduce(减少、缩小,将流的所有元素通过操作处理为1个元素)
         * min / max / count(最小 / 最大 / 总条数)
         *
         * 短路操作:
         * findFirst / findAny(返回第一个元素 / 返回任意一个元素(非并行流时基本返回第一个,但不保证一定返回第一个))
         * allMatch(判断条件里的元素,所有的都是,返回true)
         * anyMatch(判断的条件里,任意一个元素成功,返回true)
         * noneMatch(跟allMatch相反,判断条件里的元素,所有的都不是,返回true)
         */
        String str = "my name is 007";

        /**
         * forEach / forEachOrdered(遍历 / (并行流)按顺序遍历)
         */
        System.out.println("并行流使用forEach遍历:");
        str.chars().parallel().forEach(s -> System.out.print((char) s));
        System.out.println();
        System.out.println("并行流使用forEachOrdered遍历:");
        str.chars().parallel().forEachOrdered(s -> System.out.print((char) s));
        System.out.println();

        /**
         * collect / toArray(集合收集器 / 数组收集器)
         */
        System.out.println("--------------------------------------------------");
        List<String> list1 = Stream.of(str.split(" ")).collect(Collectors.toList());
        System.out.println("使用collect收集器:" + list1);
        String[] arr1 = Stream.of(str.split(" ")).toArray(i -> new String[i]);
        System.out.println("使用toArray收集器:" + Arrays.toString(arr1));

        /**
         * reduce(减少、缩小,将流的所有元素通过操作处理为1个元素)
         */
        System.out.println("--------------------------------------------------");
        Optional<String> optional = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "|" + s2);
        // optional.get()也可以获取数据,但是如果数据为null会抛出NoSuchElementException
//        String reduceStr = optional.get();
        // 所以一般用optional.orElse()来获取数据,参数传一个默认值,如果数据为null会返回默认值
        String reduceStr = optional.orElse("");
        System.out.println("使用reduce拼接字符串处理结果:" + reduceStr);
        // reduce还有种设置初始值的使用方法
        String reduceStr2 = Stream.of(str.split(" ")).reduce("", (s1, s2) -> s1 + "|" + s2);
        System.out.println("使用reduce(双参)拼接字符串处理结果:" + reduceStr2);

        // 使用reduce计算数组的字符串总长
        Integer length = Stream.of(str.split(" ")).map(String::length).reduce(0, (i1, i2) -> i1 + i2);
        System.out.println("使用reduce(双参)计算数组的字符串总长:" + length);
        // 测试reduce函数接口参数为null值(经过测试在处理过程中会报NullPointerException)
//        Optional<Integer> optional2 = Stream.of(str.split(" ")).map(s -> (Integer) null).reduce((i1, i2) -> i1 != null && i2 != null ? i1 + i2 : null);
//        System.out.println("测试reduce函数接口参数为null值:" + optional2.orElse(0));
        // 测试reduce函数接口返回null值(不会报错,length2就是null)
        Integer length2 = Stream.of(str.split(" ")).map(String::length).reduce(0, (i1, i2) -> null);
        System.out.println("测试reduce函数接口返回null值:" + length2);

        /**
         * min / max / count(最小 / 最大 / 总条数)
         */
        System.out.println("--------------------------------------------------");
        // 最小值
        Optional<String> min = Stream.of(str.split(" ")).min((s1, s2) -> s1.length() - s2.length());
        System.out.println("使用min计算数组中单词最短的:" + min.orElse(""));
        // 最大值,使用java8提供的比对器静态方法comparingInt
        Optional<String> max = Stream.of(str.split(" ")).max(Comparator.comparingInt(String::length));
        System.out.println("使用max计算数组中单词最长的:" + max.orElse(""));
        // 总条数
        long count = Stream.of(str.split(" ")).filter(s -> s.length() > 2).count();
        System.out.println("使用count计算数组中字符串长度大于2的条数:" + count);

        /**
         * findFirst / findAny(返回第一个元素 / 返回任意一个元素(非并行流时基本返回第一个,但不保证一定返回第一个))
         */
        System.out.println("--------------------------------------------------");
        // findFirst
        OptionalInt first = new Random().ints().findFirst();
        System.out.println("使用无限随机数的流,获取第一个:" + first.orElse(000));
        // findAny,使用串行流时基本都返回1,使用并行流后随即返回
        Optional<Integer> any = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6}).findAny();
//        Optional<Integer> any = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6}).parallel().findAny();
        System.out.println("在1~6的数字数组中,获取第任意一个:" + any.orElse(00));

        /**
         * allMatch(判断条件里的元素,所有的都是,返回true)
         */
        System.out.println("--------------------------------------------------");
        boolean allMatch = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6}).allMatch(i -> i > 0);
        System.out.println("使用allMatch判断数组里的数字是否都大于0:" + allMatch);
    }

}

6.并行流

public class Stream06 {

    public static void main(String[] args) {
        /**
         * 并行流:parallel()
         */
        // 串行流遍历效果
//        IntStream.range(1, 100).peek(i -> debug(i)).count();
        // 并行流遍历效果
//        IntStream.range(1, 100).parallel().peek(i -> debug(i)).count();

        /**
         * 测试:先并行操作,再串行操作
         * 结果:全都是按串行执行的
         * 结论:多次调用parallel/sequential方法,以最后一次调用为准
         */
//        IntStream.range(1, 100)
                // 调用 parallel() 产生并行流
//                .parallel().peek(i -> debug(i))
                // 调用 sequential() 产生串行流
//                .sequential().peek(i -> debug2(i))
//                .count();

        /**
         * 测试:并行流的线程池和线程数
         * 线程池:通过打印发现使用了8个线程,有主线程main和ForkJoinPool.commonPool-worker-1~7
         *        能看出并行流默认使用的是jdk提供的ForkJoinPool.commonPool线程池,默认线程数为当前设备cpu的逻辑处理器个数 - 1(这个1是主线程)
         *
         * 可以通过代码修改ForkJoinPool.commonPool线程池的默认线程数,例下面改为20个线程,此时并行流有21个线程在执行(还有主线程)
         *
         * 注:讲师说ForkJoinPool.commonPool线程池的默认线程数就是当前设备的逻辑处理器个数,我认为是这个个数 - 1(这个1是主线程)
         * 我认为main线程不属于ForkJoinPool.commonPool线程池,因为我设置了线程数20,执行的线程是21个
         */
//        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
//        IntStream.range(1, 100).parallel().peek(i -> debug(i)).count();

        /**
         * 如果多个并行流同时执行,那么使用同一个默认的线程池会出现阻塞的情况
         * 所以在一些特殊情况,我们要自己创建线程池,不使用默认的线程池,防止任务被阻塞
         *
         * 我们自己创建的线程池,线程名是以ForkJoinPool-1开头
         * 此时并行流有20个线程在执行,main线程并未参与
         */
        // 创建线程池,有20个线程
        ForkJoinPool pool = new ForkJoinPool(20);
        // 提交一个任务
        pool.submit(() -> IntStream.range(1, 100).parallel().peek(i -> debug(i)).count());
        // 关闭(不再接收新任务,执行后会自动关闭,必须调用,否则线程池一直存在)
        pool.shutdown();

        // 让主线程等待线程池的执行
        synchronized (pool) {
            try {
                pool.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void debug(int i) {
        System.out.println(Thread.currentThread().getName() + " debug: i = " + i);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void debug2(int i) {
        System.err.println(Thread.currentThread().getName() + "debug2: i = " + i);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

7.收集器

public class Stream07 {

    public static void main(String[] args) {
        // 测试数据
        List<Student> studentList = Arrays.asList(
                new Student("小明", 10, "男", "一班"),
                new Student("大明", 9, "男", "三班"),
                new Student("小白", 8, "女", "二班"),
                new Student("小黑", 13, "女", "四班"),
                new Student("小红", 7, "女", "三班"),
                new Student("小黄", 13, "男", "一班"),
                new Student("小青", 13, "女", "三班"),
                new Student("小紫", 9, "女", "二班"),
                new Student("小王", 6, "男", "一班"),
                new Student("小李", 6, "男", "一班"),
                new Student("小马", 14, "女", "四班"),
                new Student("小刘", 13, "男", "四班")
        );

        /**
         * collect() 集合收集器
         * 方法定义:<R, A> R collect(Collector<? super T, A, R> collector);
         * 其中这里的泛型所表示的含义是:
         * T:表示流中每个元素的类型。
         * A:表示中间结果容器的类型。
         * R:表示最终返回的结果类型。
         *
         * Collectors为我们提供好了很多函数接口的实现,有toList()、toSet()、toMap()、toCollection()、joining()等等...,基本满足我们的正常需求
         *
         * 除一下常用的外,还有很多,可以参考这篇文章:https://blog.csdn.net/yin__ren/article/details/79182703
         */

        /**
         * Collectors.toList()
         */
        List<Integer> collect1 = studentList.stream().map(Student::getAge).collect(Collectors.toList());
        System.out.println("使用collect(Collectors.toList())收集所有学生的年龄列表:" + collect1);

        /**
         * Collectors.toSet()
         */
        Set<Integer> collect2 = studentList.stream().map(Student::getAge).collect(Collectors.toSet());
        System.out.println("使用collect(Collectors.toSet())收集所有学生的年龄列表:" + collect2);

        /**
         * Collectors.toMap(),有3个重载的方法
         * 参数1:keyMapper:Key 的映射函数
         * 参数2:valueMapper:Value 的映射函数
         * 参数3:mergeFunction:当 Key 冲突时,调用的合并方法
         * 参数4:mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
         */
        Map<String, Integer> collect3 = studentList.stream().collect(Collectors.toMap(s -> s.getName(), s -> s.getAge()));
        System.out.println("使用collect(Collectors.toMap())收集所有学生(名字与年龄)列表:" + collect3);

        /**
         * Collectors.toCollection()
         * 使用指定集合来收集,参数是指定集合的构建方法
         */
        TreeSet<Integer> collect4 = studentList.stream().map(Student::getAge).collect(Collectors.toCollection(TreeSet::new));
        System.out.println("使用collect(Collectors.toCollection())收集所有学生的年龄列表:" + collect4);

        /**
         * Collectors.joining(),有3个重载的方法
         * 参数1:delimiter:间隔符
         * 参数2:prefix:开头符
         * 参数3:suffix:结尾符
         * 流的元素类型不对时,会报错,如果集合类型是Student对象则不能拼接
         */
        String collect5 = studentList.stream().map(Student::getName).collect(Collectors.joining(", ", "12", "34"));
        System.out.println("使用collect(Collectors.joining())拼接学生姓名字符串:" + collect5);

        /**
         * 统计汇总信息
         * summarizingInt() / summarizingLong() / summarizingDouble()
         *
         * 打印结果:年龄汇总信息IntSummaryStatistics{count=12, sum=121, min=6, average=10.083333, max=14}
         * 可以看到有总条数、总和、最小值、平均值、最大值
         */
        System.out.println("--------------------------------------------------");
        IntSummaryStatistics collect6 = studentList.stream().collect(Collectors.summarizingInt(Student::getAge));
        System.out.println("使用summarizingInt()获取年龄汇总信息:" + collect6);

        /**
         * 分块(根据规则将数据分为两块)
         * partitioningBy(),参数是断言函数接口,通过返回的boolean类型将数据分为两块
         */
        System.out.println("--------------------------------------------------");
        Map<Boolean, List<Student>> collect7 = studentList.stream().collect(Collectors.partitioningBy(s -> "男".equals(s.getGender())));
        System.out.println("使用partitioningBy()根据性别将学生分为两块:");
        System.out.println("true:" + collect7.get(true));
        System.out.println("false:" + collect7.get(false));

        /**
         * 分组(根据规则将数据分为N组)
         * groupingBy(Function<? super T, ? extends K> classifier)
         * Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
         * 其中这里的泛型所表示的含义是:
         * K:表示分组的key值类型。
         * T:表示流中每个元素的类型。
         * A:表示中间结果容器的类型。
         * R:表示最终返回的结果类型。
         */
        System.out.println("--------------------------------------------------");
        Map<String, List<Student>> collect8 = studentList.stream().collect(Collectors.groupingBy(Student::getGrade));
        System.out.println("groupingBy()根据班级将学生分为N组:");
        System.out.println("一班:" + collect8.get("一班"));
        System.out.println("二班:" + collect8.get("二班"));

        Map<String, Long> collect9 = studentList.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
        System.out.println("groupingBy()根据班级将学生分为N组,得出每组的人数:");
        System.out.println("一班:" + collect9.get("一班"));
        System.out.println("二班:" + collect9.get("二班"));
    }

}

class Student {

    private String name;
    private Integer age;
    private String gender;
    private String grade;

    public Student(String name, Integer age, String gender, String grade) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.grade = grade;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", grade=" + grade +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String getGrade() {
        return grade;
    }
    public void setGrade(String grade) {
        this.grade = grade;
    }

}

8.Stream的运行机制

public class Stream08 {

    public static void main(String[] args) {
        Random random = new Random();
        // 随机产生数据
        Stream<Integer> integerStream = Stream.generate(random::nextInt)
                // 产生500个(无限流需要短路操作)
                .limit(500)
                // 第一个无状态操作
                .peek(i -> print("peek:" + i))
                // 第二个无状态操作
                .filter(i -> {
                    print("filter:" + i);
                    return i > 1000000;
                })
                // 第三个有状态操作
                .sorted((i1, i2) -> {
                    print("排序:" + i1 + ", " + i2);
                    return i1.compareTo(i2);
                })
                // 第四个无状态操作
                .peek(i -> print("peek2:" + i))
                // 使用并行
                .parallel();
        // 终止操作(不调用不会执行中间操作)
        integerStream.count();

        /**
         * 两个中间操作时打印:
         * peek:208512162
         * filter:208512162
         * peek:-416027507
         * filter:-416027507
         * ...
         *
         * 结论:
         * 1.所有操作是链式调用,一个元素只迭代一次
         * 2.每个中间操作返回一个新的流,流里面有一个属性(sourceStage),执行同一个地方,就是Head
         * 3.Head -> nextStage -> nextStage -> ... -> null
         */

        /**
         * 四个中间操作时打印:
         * peek:208512162
         * filter:208512162
         * peek:-416027507
         * filter:-416027507
         * ...
         * 排序:208512162, 1819239254
         * 排序:208512162, -416027507
         * ...
         * peek2:208512162
         * peek2:-416027507
         *
         * 结论:
         * 4.有状态操作会把无状态操截断,单独处理
         *
         *
         * 测试:使用并行流执行时,有状态的中间操作只有main线程在执行
         *
         * 结论:
         * 5.并行环境下,有状态的中间操作不一定能并行操作(官方没有表示有状态的中间操作不会并发执行,但现在测试是不会并发执行的)
         * 6.parallel()和sequential()也属于中间操作(也返回流),但他们不创建流,他们只修改Head的并行标志(parallel)
         */
    }

    private static void print(String s) {
        System.out.println(Thread.currentThread().getName() + ":" + s);
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容