三、Stream流式编程

一、基础知识

Stream其实就是一个高级的迭代器,它不是一个数据结构,不是一个集合,不会存放数据。它只关注怎么高效处理数据,把数据放入一个流水线中处理。

1、内部迭代和外部迭代

(1)外部迭代

就是平时我们在对集合或者数组中的数据进行处理时,比如我们求一个数组中所有数字的和,那么我们需要先定义一个外部变量SUM,然后遍历数据,取出数组中的值加在SUM上面。

public static void main(String[] args) {
		int[] nums = { 1, 2, 3 };
		// 外部迭代
		int sum = 0;
		for (int i : nums) {
			sum += i;
		}
		System.out.println(sum);
	}

(2)内部迭代

利用Stream流编程或者lambda表达式来进行迭代,相对于外部迭代,我们不需要关注它怎么样去处理数据,我们只需要给它数据,然后告诉它我们想要的结果就可以了。不要想外部迭代那样自己定义一些变量。

public static void main(String[] args) {
		int[] nums = { 1, 2, 3 };
		//内部迭代
		int sum = IntStream.of(nums).sum();
		System.out.println(sum);
	}

(3)两者的区别

1.内部迭代相对来说比较简短,不用关注那么多细节。
2.外部迭代是一个串行的操作,如果数据量过大,性能可能会受影响,这样我们可能就需要自己去做线程池,做拆分。内部迭代的话可以用并行流,达到并行操作,不用开发人员去关系线程的问题。

2、中间操作、终止操作和惰性求值

(1)中间操作

中间操作就是返回stream流的操作,之前说过stream操作就像流水线一样,在流的整过操作过程中会不断返回一个流。比如map()等操作。

    public static void testMap() {
        Stream<String> strStream = Arrays.stream(new String[]{"1", "2", "3"});
        strStream.map(Integer::parseInt).forEach(System.out::println);
    }
(2)终止操作

终止操作返回的是一个结果,就是所有中间操作做完以后进行的一个操作,比如汇总,求和,遍历输出等等操作。

    public static void testMap() {
        Stream<String> strStream = Arrays.stream(new String[]{"1", "2", "3"});
        strStream.map(Integer::parseInt).forEach(System.out::println);
    }
(3)惰性求值

惰性求值就是在不进行终止操作的情况下,中间操作是不会执行的。

public class TestStream {

	public static void main(String[] args) {
		int[] nums = { 1, 2, 3 };

		int sum = IntStream.of(nums).map(TestStream::addNum).sum();
		System.out.println(sum);
		System.out.println("惰性求值就是在不进行终止操作的情况下,中间操作是不会执行的");
		IntStream.of(nums).map(TestStream::addNum);
	}

	private static int addNum(int i) {
		System.out.println("执行了+2操作");
		return i + 2;
	}
}

测试结果:

执行了+2操作
执行了+2操作
执行了+2操作
12
惰性求值就是在不进行终止操作的情况下,中间操作是不会执行的

可以看出我们在不进行sum这个终止操作,方法引用中的方法时不会执行的,即map中间操作不会执行。

二、基本操作

(一)流的创建

一般常规的完整流操作,包括创建流,中间操作,然后终止操作。以下是常见的几种流创建方式:
在这里插入图片描述
示例:

    /**
     * 创建流
     */
    public static void createStream() {
        ArrayList<String> list = new ArrayList<>();
        // 从集合创建流
        list.stream();
        // 并行流
        list.parallelStream();

        //从数组创建流
        Arrays.stream(new int[] {1, 2, 3});

        //创建数字流(就是利用Arrays.stream()创建流)
        IntStream.of(1, 2, 3);
        // 不包括5
        IntStream.range(1, 5);
        // 包括5
        IntStream.rangeClosed(1, 5);
        System.out.println("----");

        // 创建无限流,无限流需要用limit控制大小,不然使用报错
        new Random().ints().limit(10).forEach(System.out::println);

        // Stream自己生产流
        Stream.generate(() -> new ArrayList());
    }

(二)流的中间操作

流的中间操作包含两种,一类是无状态操作,一类是有状态操作。
在这里插入图片描述
在学习下面内容之前,先谈下对函数式接口的理解:

理解一下函数式接口参数:
函数式接口参数主要由6个基础接口为基,包括:

  1. UnaryOperator<T>:代表结果与参数类型一致
  2. BinaryOperator<T>:代表结果与参数类型一致
  3. Predicate<T>:代表带有一个参数并返回一个boolean的函数
  4. Function<T, R>:代表其参数与返回的类型不一致的函数
  5. Supplier<T>:代表没有参数并且返回一个值的函数
  6. Consumer<T>:代表带有一个函数但不返回任何值的函数,相当于消耗掉了参数

Stream操作API大多需要这六种形式的参数。若是以前没lambda表达式,一般是使用匿名内部类的形式,比如使用 peek() 方法打印所有单词,其接受一个 Consumer<T> 类型的参数:

          strStream.peek(new Consumer<String>() {
             @Override
              public void accept(String s) {
                   System.out.println(s);
              }
          }).forEach(System.out::println);

而有了lambda,上面的代码就可以简化成:strStream.peek(str -> System.out.println(str)).forEach(System.out::println);,这里简单解析下:
Consumer<T>是一个函数式接口,作为参数时,需要一个实体化类,所以是 new Consumer<String>(){},但是这种函数式接口一般需要实现一个方法,对于 Consumer<T> 那就是 accept(String s) 方法。又因为是 Stream<String> 类型,所以我们在简化lambda 表达式时 str -> System.out.println(str),就可以推断出 str 的类型为 String,符合上面的范式要求。

1、无状态操作

所谓无状态就是一次操作,不能保存数据,线程安全。流中的map,filter这类操作就是无状态,这些操作只是从输入流中获取每一个元素,并且在输出流中得到一个结果,元素和元素之间没有依赖关系,不需要存储数据。

(1)map映射

将流中的元素映射成另外一种元素,接受一个Function<T, R>类型的参数:代表其参数与返回的类型不一致的函数。

    /**
     * map映射:将流中的元素映射成另外一种元素,接受一个Function类型的参数。(PS:可以改变流中的元素类型)
     */
    public static void testMap() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});

//        strStream.map(str -> str.toUpperCase()).forEach(System.out::println);
        // 上面简化成这样
        strStream.map(String::toUpperCase).forEach(System.out::println);
    }
I
LOVE
YOU
(2)flatMap映射

flatMap是一种拉平映射,接受一个Function<T, R>类型的参数,flatMap A->B属性(是一个集合),最终得到A元素中的所有B属性集合。

    /**
     * flatMap映射:flatMap是一种拉平映射,接受一个Function类型的参数,flatMap A->B属性(是一个集合),
     *              最终得到A元素中的所有B属性集合。
     */
    public static void testFlatMap() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});
        /*
         * flatMap拉平映射:获取流中元素的字符集合
         * 因为flatMap的参数Function的输出是一个Steam,但是String.chars()返回的是一个IntStream
         * 而IntStream/LongStream并不是Stream的子类,所以要进行装箱boxed
         */
        strStream.flatMap(str -> str.chars().boxed()).forEach(i -> System.out.println((char) i.intValue()));
    }
I
L
o
v
e
Y
o
u
(3)map和flatmap的区别

对于stream,两者的输入都是stream的每一个元素,输出各有不同:

  • map的输出一个元素(null也是要返回)
  • flatmap输出0或者多个元素(为null的时候其实就是0个元素)

flatmap的意义在于,一般的java方法都是返回一个结果,但是对于结果数量不确定的时候,用map这种java方法的方式,是不太灵活的,所以引入了flatmap。

    /**
     * 洗牌
     * @param suits 花色
     * @param ranks 数字
     */
    public static void initCard(Map<String, String> suits, Map<String, String> ranks) {
        Stream.of(suits.values()).flatMap(suit -> Stream.of(ranks.values()).map(rank -> new Card(suit, rank)));
    }

以上述洗牌代码为例:洗牌一般是进行花色+数字组合,按照传统模式,那就是先遍历花色,再遍历数字,同时组合成一张牌

		List<Card> result = new ArrayList<>();
		//1个参数对应多个结果,用 flatMap() 方法代替
        for (Suit suit : suits.values()) {
        	//1个参数对应1个结果,用 map() 方法代替
            for (Rank rank : ranks.values()) {
                result.add(new Card(suit, rank));
            }
        }

案例解析:遍历suits.values()就是遍历花色,遍历ranks.values()就是遍历数字,再遍历数字一遍完毕后,比如当前花色是红色,那么就是红色A-K,这里出现的结果就是一个参数对应多个结果,也就是flatMap();而在遍历数字时,每遍历一个数字,就初始化一张牌,也就是一个参数只对应一个结果,也就是map()

(4)filter过滤

对流中的元素进行过滤操作,接受一个Predicate<T>类型的参数:代表带有一个参数并返回一个boolean的函数

    /**
     * filter过滤:对流中的元素进行过滤操作,接受一个Predicate类型的参数
     * 备注:
     *     Predicate<T>接口:代表带有一个参数并返回一个boolean的函数
     */
    public static void testFilter() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});
        /*
         * filter过滤出长度大于2的元素
         */
        strStream.filter(str -> str.length() > 2).forEach(System.out::println);
    }
Love
You
(5)peek 操作元素

它提供了一种对流中所有元素操作的方法,由于是中间操作,它不会把流消费掉,那么就可以进行流操作。而不像Foreach等终止操作,虽然也能操作流中的元素,但是会把流给消费掉。接受一个Consumer<T>类型的参数:代表带有一个函数但不返回任何值的函数,相当于消耗掉了参数。

    /**
     * peek 操作元素:
     *     它提供了一种对流中所有元素操作的方法,由于是中间操作,它不会把流消费掉,那么就可以进行流操作。
     * 而不像Foreach等终止操作,虽然也能操作流中的元素,但是会把流给消费掉。接受一个Consumer类型的参数。
     */
    public static void testPeek() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});
        strStream.peek(System.out::println).forEach(System.out::println);
    }
I
I
Love
Love
You
You
(6)map与peek的区别

map()的参数是Function<? super T, ? extends R> mapper ,是一个Function函数,输入T返回R,所以对于map来说是有返回值得,即对流中元素进行操作后可以返回变化了的元素到流中。
peek()的参数是Consumer<? super T> action,是一个Consumer函数,输入T而没有返回。所以peek操作没法改变流中的元素,只能做一些输出,外部处理操作。

    public static void testCompare() {
        Stream<String> strStream1 = Arrays.stream(new String[]{"I", "Love", "You"});
        strStream1.map(String::toUpperCase).forEach(System.out::println);
        System.out.println("----------");
        Stream<String> strStream2 = Arrays.stream(new String[]{"I", "Love", "You"});
        strStream2.peek(String::toUpperCase).forEach(System.out::println);
    }
I
LOVE
YOU
----------
I
Love
You
(7)unordered 无序化

unordered() 操作不会执行任何操作来显式地对流进行排序。它的作用是消除了流必须保持有序的约束,从而允许后续操作使用不必考虑排序的优化。
在流有序时, 但用户不特别关心该顺序的情况下,使用 unordered 明确地对流进行去除有序约束可以改善某些有状态或终端操作的并行性能。

    /**
     * unordered 无序化:
     *     unordered 操作不会执行任何操作来显式地对流进行排序。它的作用是消除了流必须保持有序的约束,从而允许后续操作
     * 使用不必考虑排序的优化。
     *     在流有序时, 但用户不特别关心该顺序的情况下,使用 unordered 明确地对流进行去除有序约束可以改善某些有状态或
     * 终端操作的“并行性能”:parallel()。
     */
    public static void testUnordered() {
        Stream.of(5, 3, 7, 2).unordered().forEach(System.out::println);
        System.out.println("----");
        Stream.of(5, 3, 7, 2).unordered().parallel().forEach(System.out::println);

    }
5
3
7
2
----
7
2
3
5
2、有状态操作

所谓有状态就是有数据存储功能,线程不安全。流中的sort、distinct、limit、skip这类操作就是有状态的,这些操作需要先知道先前的历史数据,而且需要存储一些数据,元素之间有依赖关系。

(1)distinct去重

对流中的元素进行去重,无参数。

    public static void testDistinct() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "I", "Love", "You"});
        strStream.distinct().forEach(System.out::println);
    }
I
Love
You
(2)sorted排序

对流中的元素进行排序:不带参数的是按照自然顺序进行排序;带参数的会传一个Comparator<? super T>类型的参数,作为比较规则。
Comparator是在jdk8之前就存在的一个函数式接口,入参为两个要比较的元素,返回的是一个integer,负数标识第一个参数小于第二参数,0标识等于,正数标识大于。

    public static void testSorted() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});
        // 按长短排序
        strStream.sorted((s1, s2) -> s2.length() - s1.length()).forEach(System.out::println);
    }
Love
You
I
(3)limit限流

获取流中前n个元素返回,在用无限流的时候可以起到限流的作用。

    public static void testLimit() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});
        // limit获取前2个元素
        strStream.limit(2).forEach(System.out::println);
    }
I
Love
(4)skip跳过

跳过流中的前n个元素。

    public static void testSkip() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You"});
        // 跳过前2个元素
        strStream.skip(2).forEach(System.out::println);
    }
You
(5)limit配合skip达成分页效果

对于不方便用分页插件和手写分页的情况,可以使用limit+skip的形式对结果集进行分页处理。

    public static void testPage() {
        Stream<String> strStream = Arrays.stream(new String[]{"I", "Love", "You", "Do", "You", "Know"});
        long pageSize = 2;
        long pageNumber = 2;
        strStream.skip(pageSize * pageNumber).limit(pageSize).forEach(System.out::println);
    }
You
Know

(三)流的终止操作

在这里插入图片描述

1、非短路操作

非短路操作可以批量处理数据,但是需要处理完全部元素才会返回结果。

(1)forEach/forEachOrdered

forEach()用的很多了,就是遍历数据。forEachOrdered()也一样,只是它一般在并行流时用,可以保持元素的顺序。

    public static void testForEach() {
        Stream<String> strStream1 = Arrays.stream(new String[]{"I", "Love", "You"});
        // forEach():并行流(不能保证顺序)
        strStream1.parallel().forEach(System.out::println);
        System.out.println("-------");
        Stream<String> strStream2 = Arrays.stream(new String[]{"I", "Love", "You"});
        // forEachOrdered():并行流(保证顺序)
        strStream2.parallel().forEachOrdered(System.out::println);
    }
Love
You
I
-------
I
Love
You
(2)collect/toArray

collect()可以对流中元素执行一个可变汇聚操作,比如:将流中的元素放入到一个List集合当中,将流中的元素进行分组、分区,求和等等操作。接受一个收集器Collector对象。

    public static void testCollect() {
        List<String> list = Arrays.stream(new String[]{"I", "Love", "You"}).collect(Collectors.toList());
        System.out.println(list);
    }
[I, Love, You]

toArray()相对来说比较简单,即将流中的数据收集成一个Object类型的数组

    public static void testToArray() {
        Object[] array = Arrays.stream(new String[]{"I", "Love", "You"}).toArray();
        System.out.println(Arrays.toString(array));
    }
(3)reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

示例1:Optional<T> reduce(BinaryOperator<T> accumulator)

    public static void testReduce() {
        String str = "I Love You";
        Optional<String> result = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "|" + s2);
        System.out.println(result.orElse(""));
    }
I|Love|You

Optional是jdk8中新增的类,它对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。
我们在使用reduce时可以设置初始值,这样就不会返回Optional。
示例2:T reduce(T identity, BinaryOperator<T> accumulator)
参数一:identity 初始值
参数二: BinaryOperator<T> 代表结果与参数类型一致的函数,方法签名为:T apply(T t1, T t2)

    public static void testReduce() {
        String str = "I Love You";
        String result = Stream.of(str.split(" ")).reduce("", (s1, s2) -> s1 + "|" + s2);
        System.out.println(result);
    }
|I|Love|You
(4)min、max、count

求最大值最小值,计算流中元素的个数。min、max的参数是一个Comparator,之前已经说过,其实就是一个比较器函数。

    public static void testCompar() {
        String str = "I Love You";
        Optional<String> min = Stream.of(str.split(" ")).min((s1, s2) -> s1.length() - s2.length());
        System.out.println(min.orElse(""));
        Optional<String> max = Stream.of(str.split(" ")).max((s1, s2) -> s1.length() - s2.length());
        System.out.println(max.orElse(""));
    }
I
Love
2、短路操作

短路操作是指不用处理全部元素就可以返回结果。短路操作必须一个元素处理一次。

(1)findFirst、 findAny

findFirst()表示找到第一个元素。找到了就直接返回,不在遍历后面元素。
findAny()表示找到任何一个元素就会返回。

(2)anyMatch、 allMatch、 noneMatch

allMatch:Stream 中全部元素符合传入的 Predicate<T>,返回 true,只要有一个不满足就返回false
anyMatch:Stream 中只要有一个元素符合传入的 Predicate<T>,返回 true。否则返回false
noneMatch:Stream 中没有一个元素符合传入的 Predicate<T>,返回 true。只要有一个满足就返回false

    public static void testMatch() {
        String str = "I Love You";
        
        // allMatch
        Stream<String> stream1 = Stream.of(str.split(" "));
        boolean allMatch = stream1.allMatch(s -> s.contains("o"));
        System.out.println(allMatch);

        // anyMatch
        Stream<String> stream2 = Stream.of(str.split(" "));
        boolean anyMatch = stream2.anyMatch(s -> s.contains("o"));
        System.out.println(anyMatch);

        // noneMatch
        Stream<String> stream3 = Stream.of(str.split(" "));
        boolean noneMatch = stream3.noneMatch(s -> s.contains("o"));
        System.out.println(noneMatch);
    }
false
true
false

(三)并行流

1.获取并行流

可以通过流的parallel()来获取一个并行流,对于获取并行流,有几点需要注意。

  1. 并行流需要环节支持,不然就算获取了并行流,反而运行效率会变低。
  2. 多次调用parallel()sequential()(获取串行流),以最后一次为准。
2.并行流的线程池

流的并行操作的线程数默认是机器的CPU个数。默认使用的线程池是ForkJoinPool.commonPool线程池。

    public static void testParallelThreadPool() {
        IntStream.range(1, 20).parallel().peek(StreamDemo::printThreads).count();
    }

    public static void printThreads(int i) {
        System.out.println(Thread.currentThread().getName() + "[" + i + "]");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
main[12]
ForkJoinPool.commonPool-worker-2[17]
ForkJoinPool.commonPool-worker-3[3]
ForkJoinPool.commonPool-worker-1[6]
ForkJoinPool.commonPool-worker-2[19]
ForkJoinPool.commonPool-worker-3[4]
main[14]
ForkJoinPool.commonPool-worker-1[5]
ForkJoinPool.commonPool-worker-2[18]
ForkJoinPool.commonPool-worker-3[2]
main[13]
ForkJoinPool.commonPool-worker-1[8]
ForkJoinPool.commonPool-worker-2[16]
ForkJoinPool.commonPool-worker-3[1]
main[11]
ForkJoinPool.commonPool-worker-1[9]
ForkJoinPool.commonPool-worker-2[15]
main[10]
ForkJoinPool.commonPool-worker-3[7]

使用自己的线程池可以防止当前流操作被其他流操作的线程阻塞

    public static void testCustomThreadPool() {
        ForkJoinPool pool = new ForkJoinPool(10);
        pool.submit(() -> IntStream.range(1, 20).parallel().peek(StreamDemo::printThreads).count());
        pool.shutdown();
        synchronized (pool) {
            try {
                pool.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
ForkJoinPool-1-worker-15[14]
ForkJoinPool-1-worker-11[17]
ForkJoinPool-1-worker-13[3]
ForkJoinPool-1-worker-1[10]
ForkJoinPool-1-worker-2[6]
ForkJoinPool-1-worker-6[2]
ForkJoinPool-1-worker-9[12]
ForkJoinPool-1-worker-4[11]
ForkJoinPool-1-worker-10[4]
ForkJoinPool-1-worker-8[1]
ForkJoinPool-1-worker-2[5]
ForkJoinPool-1-worker-15[13]
ForkJoinPool-1-worker-11[19]
ForkJoinPool-1-worker-4[8]
ForkJoinPool-1-worker-8[7]
ForkJoinPool-1-worker-10[9]
ForkJoinPool-1-worker-1[16]
ForkJoinPool-1-worker-13[15]
ForkJoinPool-1-worker-6[18]

(四)收集器

对流中元素执行一个可变汇聚操作,是一个终止操作。比如:将流中的元素放入到一个List集合当中,将流中的元素进行分组、分区,求和等等操作。接受一个收集器Collector对象。常用的收集器有以下几种:

1.toList/toSet

收集成一个List集合/Set集合,当然也可以指定集合类型

public static void main(String[] args) {
		List<Student> students = Arrays.asList(
			new Student("小明", 10, Gender.MALE, Grade.ONE),
			new Student("大明", 9, Gender.MALE, Grade.THREE),
			new Student("小白", 8, Gender.FEMALE, Grade.TWO),
			new Student("小黑", 13, Gender.FEMALE, Grade.FOUR),
			new Student("小红", 7, Gender.FEMALE, Grade.THREE),
			new Student("小黄", 13, Gender.MALE, Grade.ONE),
			new Student("小青", 13, Gender.FEMALE, Grade.THREE),
			new Student("小紫", 9, Gender.FEMALE, Grade.TWO),
			new Student("小王", 6, Gender.MALE, Grade.ONE),
			new Student("小李", 6, Gender.MALE, Grade.ONE),
			new Student("小马", 14, Gender.FEMALE, Grade.FOUR),
			new Student("小刘", 13, Gender.MALE, Grade.FOUR));
		//收集成list
		List<Integer> ageList = students.stream().map(Student::getAge).collect(Collectors.toList());
		System.out.println("所有学生的年龄:"+ageList);
		//收集成set
		Set<Integer> ageSet = students.stream().map(Student::getAge).collect(Collectors.toSet());
		System.out.println("所有学生的年龄-去重:"+ageSet);
		//指定集合treeset
		 TreeSet<Integer> ageTreeSet = students.stream().map(Student::getAge).collect(Collectors.toCollection(TreeSet::new));
		System.out.println("所有学生的年龄-去重-treeSet:"+ageTreeSet);
	}

所有学生的年龄:[10, 9, 8, 13, 7, 13, 13, 9, 6, 6, 14, 13]
所有学生的年龄-去重:[6, 7, 8, 9, 10, 13, 14]
所有学生的年龄-去重-treeSet:[6, 7, 8, 9, 10, 13, 14]

2.summarizingInt/Double/Long 汇总信息

统计汇总信息

    public static void testSummaryStatistics() {
        List<Integer> numbers = Arrays.asList(2, 4, 6, 89, 98, 23, 65);
        IntSummaryStatistics statistics = numbers.stream().collect(Collectors.summarizingInt(i -> i));
        System.out.println("汇总信息:" + statistics);
    }
汇总信息:IntSummaryStatistics{count=7, sum=287, min=2, average=41.000000, max=98}
2.partitioningBy 分块

将数据按一定规则分成两块,输入参数是一个Predicate<T>

    public static void testPartitioningBy() {
        List<Integer> numbers = Arrays.asList(2, 4, 6, 89, 98, 23, 65);
        Map<Boolean, List<Integer>> resultMap = numbers.stream().collect(Collectors.partitioningBy(i -> i > 10));
        System.out.println("结果列表:" + resultMap);
    }
结果列表:{false=[2, 4, 6], true=[89, 98, 23, 65]}
3.groupingBy 分组

将数据按一定规则分成几组,相对于分块,分组要灵活很多,输入参数是一个Function<T, R>

public static void main(String[] args) {
		List<Student> students = Arrays.asList(
			new Student("小明", 10, Gender.MALE, Grade.ONE),
			new Student("大明", 9, Gender.MALE, Grade.THREE),
			new Student("小白", 8, Gender.FEMALE, Grade.TWO),
			new Student("小黑", 13, Gender.FEMALE, Grade.FOUR),
			new Student("小红", 7, Gender.FEMALE, Grade.THREE),
			new Student("小黄", 13, Gender.MALE, Grade.ONE),
			new Student("小青", 13, Gender.FEMALE, Grade.THREE),
			new Student("小紫", 9, Gender.FEMALE, Grade.TWO),
			new Student("小王", 6, Gender.MALE, Grade.ONE),
			new Student("小李", 6, Gender.MALE, Grade.ONE),
			new Student("小马", 14, Gender.FEMALE, Grade.FOUR),
			new Student("小刘", 13, Gender.MALE, Grade.FOUR));

		Map<Grade, List<Student>> grades = students.stream()
				.collect(Collectors.groupingBy(Student::getGrade));
		MapUtils.verbosePrint(System.out, "学生班级列表", grades);
	}

学生班级列表 = 
{
    THREE = [Student [name=大明, age=9, gender=MALE, grade=THREE], Student [name=小红, age=7, gender=FEMALE, grade=THREE], Student [name=小青, age=13, gender=FEMALE, grade=THREE]]
    ONE = [Student [name=小明, age=10, gender=MALE, grade=ONE], Student [name=小黄, age=13, gender=MALE, grade=ONE], Student [name=小王, age=6, gender=MALE, grade=ONE], Student [name=小李, age=6, gender=MALE, grade=ONE]]
    FOUR = [Student [name=小黑, age=13, gender=FEMALE, grade=FOUR], Student [name=小马, age=14, gender=FEMALE, grade=FOUR], Student [name=小刘, age=13, gender=MALE, grade=FOUR]]
    TWO = [Student [name=小白, age=8, gender=FEMALE, grade=TWO], Student [name=小紫, age=9, gender=FEMALE, grade=TWO]]
}

groupingBy() 分组以后,可以看到每一组还是一个集合,这个集合也是可以进行收集操作的。groupingBy 有一个重载的方法
groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream),第二个参数是一个Collector

public static void main(String[] args) {
		List<Student> students = Arrays.asList(
			new Student("小明", 10, Gender.MALE, Grade.ONE),
			new Student("大明", 9, Gender.MALE, Grade.THREE),
			new Student("小白", 8, Gender.FEMALE, Grade.TWO),
			new Student("小黑", 13, Gender.FEMALE, Grade.FOUR),
			new Student("小红", 7, Gender.FEMALE, Grade.THREE),
			new Student("小黄", 13, Gender.MALE, Grade.ONE),
			new Student("小青", 13, Gender.FEMALE, Grade.THREE),
			new Student("小紫", 9, Gender.FEMALE, Grade.TWO),
			new Student("小王", 6, Gender.MALE, Grade.ONE),
			new Student("小李", 6, Gender.MALE, Grade.ONE),
			new Student("小马", 14, Gender.FEMALE, Grade.FOUR),
			new Student("小刘", 13, Gender.MALE, Grade.FOUR));

		Map<Grade, Long> gradesCount = students.stream()
				.collect(Collectors.groupingBy(Student::getGrade,Collectors.counting()));
		MapUtils.verbosePrint(System.out, "班级学生个数列表", gradesCount);
	}

班级学生个数列表 = 
{
    FOUR = 3
    TWO = 2
    ONE = 4
    THREE = 3
}

(五)Stream的运行机制

1.所有的操作都是链式调用,一个元素只迭代一次。
public static void main(String[] args) {
		Stream.generate(() -> new Random().nextInt()).limit(10)
				// 第一个无状态操作
				.peek(s -> print("peek:" + s))
				// 第二个无状态操作
				.filter(s -> {
					print("filter:" + s);
					return s > 1000000;
				})
				// 终止操作
				.count();
	}

	private static void print(String string) {
		System.err.println(string);
	}

先执行peek,再执行filter

peek:1371982675
filter:1371982675
peek:-1145718874
filter:-1145718874
peek:-878943922
filter:-878943922
peek:1584470948
filter:1584470948
peek:-1035083327
filter:-1035083327
peek:1305239996
filter:1305239996
peek:-521348180
filter:-521348180
peek:-707884835
filter:-707884835
peek:1158291746
filter:1158291746
peek:581375981
filter:581375981

2.每一个中间操作返回一个新的流, 每个流里面有一个属性sourceStage指向同一个地方-链表的头Head

在这里插入图片描述

3.在Head里面就会指向nextStage,head->nextStage->nextStage->…->null

在这里插入图片描述

4.有状态操作会把无状态操作截断,单独处理。
    public static void testMixedFunction() {
        Stream.generate(() -> new Random().nextInt())
                .limit(10)
                // 第一个无状态操作
                .peek(s -> System.err.println("peek1:" + s))
                // 第二个无状态操作
                .filter(s -> {
                    System.err.println("filter:" + s);
                    return s > 1000000;
                })
                //有状态操作
                .sorted((i1, i2) -> {
                    System.err.println("排序:" + i1 + "|" + i2);
                    return i1.compareTo(i2);
                })
                //又一个无状态操作
                .peek(s -> System.err.println("peek2:" + s))
                // 终止操作
                .count();
    }
peek1:-1844476532
filter:-1844476532
peek1:377847868
filter:377847868
peek1:-1885671594
filter:-1885671594
peek1:-2083392154
filter:-2083392154
peek1:184596909
filter:184596909
排序:184596909|377847868
peek2:184596909
peek2:377847868
5.并行环节下,有状态的中间操作不一定能并行操作,并且,被有状态操作隔断的之前的无状态操作也不一定能并行操作。如下,其中.parallel()在中间操作的最后加上和开始就加上,效果是一样的。
public static void main(String[] args) {
		Stream<Integer> stream = Stream.generate(() -> new Random().nextInt()).limit(10)
				// 第一个无状态操作
				.peek(s -> print("peek1:" + s))
				// 第二个无状态操作
				.filter(s -> {
					print("filter:" + s);
					return s > 1000000;
				})
				//有状态操作
				.sorted((i1,i2)->{
					print("排序:"+i1+","+i2);
					return i1.compareTo(i2);
				})
				//又一个无状态操作
				.peek(s->print("peek2:"+s))
				//并行
				.parallel();
		
		stream.count();
	}

	private static void print(String string) {
		System.err.println(Thread.currentThread().getName()+"->"+string);
	}

可以看到peek1和filter这两个无状态操作不是并行运行的,使用的是一个线程ForkJoinPool.commonPool-worker-3。排序是在main线程中执行的。

ForkJoinPool.commonPool-worker-3->peek1:-1579655556
ForkJoinPool.commonPool-worker-3->filter:-1579655556
ForkJoinPool.commonPool-worker-3->peek1:952457888
ForkJoinPool.commonPool-worker-3->filter:952457888
ForkJoinPool.commonPool-worker-3->peek1:-863671158
ForkJoinPool.commonPool-worker-3->filter:-863671158
ForkJoinPool.commonPool-worker-3->peek1:-399451653
ForkJoinPool.commonPool-worker-3->filter:-399451653
ForkJoinPool.commonPool-worker-3->peek1:1680077762
ForkJoinPool.commonPool-worker-3->filter:1680077762
ForkJoinPool.commonPool-worker-3->peek1:-395699073
ForkJoinPool.commonPool-worker-3->filter:-395699073
ForkJoinPool.commonPool-worker-3->peek1:769813179
ForkJoinPool.commonPool-worker-3->filter:769813179
ForkJoinPool.commonPool-worker-3->peek1:151702238
ForkJoinPool.commonPool-worker-3->filter:151702238
ForkJoinPool.commonPool-worker-3->peek1:2003058652
ForkJoinPool.commonPool-worker-3->filter:2003058652
ForkJoinPool.commonPool-worker-3->peek1:698152607
ForkJoinPool.commonPool-worker-3->filter:698152607
main->排序:1680077762,952457888
main->排序:769813179,1680077762
main->排序:769813179,1680077762
main->排序:769813179,952457888
main->排序:151702238,952457888
main->排序:151702238,769813179
main->排序:2003058652,952457888
main->排序:2003058652,1680077762
main->排序:698152607,952457888
main->排序:698152607,769813179
main->排序:698152607,151702238
ForkJoinPool.commonPool-worker-3->peek2:2003058652
main->peek2:952457888
ForkJoinPool.commonPool-worker-3->peek2:1680077762
ForkJoinPool.commonPool-worker-1->peek2:151702238
ForkJoinPool.commonPool-worker-2->peek2:698152607
main->peek2:769813179

6.parallel/sequetial这两个操作也是中间操作,但是他们不创建流,他们只修改流中Head的并行标志。

在这里插入图片描述
参见Stream流编程学习笔记

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值