Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。

    image.png

    在没有Java8 stream之前,我们完成以下需求需要如此操作。

    1. //获取Transaction的类型等于Transaction.GROCERY的对象,将所有满足条件的对象装入一个集合(过滤)
    2. List<Transaction> groceryTransactions = new Arraylist<>();
    3. for(Transaction t: transactions){
    4. if(t.getType() == Transaction.GROCERY){
    5. groceryTransactions.add(t);
    6. }
    7. }
    8. //根据某一个字段的值对集合进行排序
    9. Collections.sort(groceryTransactions, new Comparator(){
    10. public int compare(Transaction t1, Transaction t2){
    11. return t2.getValue().compareTo(t1.getValue());
    12. }
    13. });
    14. //将集合中对象的id字段找出来。
    15. List<Integer> transactionIds = new ArrayList<>();
    16. for(Transaction t: groceryTransactions){
    17. transactionsIds.add(t.getId());
    18. }

    那么再拥有Java8 stream之后

    1. List<Integer> transactionsIds = transactions.parallelStream().
    2. filter(t -> t.getType() == Transaction.GROCERY).
    3. sorted(comparing(Transaction::getValue).reversed()).
    4. map(Transaction::getId).
    5. collect(toList());

    构建一个流对象

    1. //将集合转成一个Stream对象
    2. List<String> list = new ArrayList<>();
    3. Stream<String> stream = list.stream(); //获取一个顺序流
    4. Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
    5. //将数组转成一个Stream对象
    6. Integer[] nums = new Integer[10];
    7. Stream<Integer> stream = Arrays.stream(nums);
    8. //通过Stream的静态方法转换成流对象
    9. Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    10. Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
    11. stream2.forEach(System.out::println); // 0 2 4 6 8 10

    流对象的操作分类
    Stream的操作大体上分为两种:中间操作和终止操作。
    流的中间操作
    1)筛选与切片
    filter:过滤流中的某些元素
    limit(n):获取n个元素
    skip(n):跳过n元素,配合limit(n)可实现分页
    distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素

    1. Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
    2. Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
    3. .distinct() //6 7 9 8 10 12 14
    4. .skip(2) //9 8 10 12 14
    5. .limit(2); //9 8
    6. newStream.forEach(System.out::println);

    2)映射
    将集合中每一个对象的某一个属性抽出来组成一个list或set。

    1. List<Integer> transactionsIds = transactions.parallelStream().
    2. map(Transaction::getId).
    3. collect(toList());

    3)排序
    sorted():自然排序,流中元素需实现Comparable接口
    sorted(Comparator com):定制排序,自定义Comparator排序器

    1. List<String> list = Arrays.asList("aa", "ff", "dd");
    2. //String 类自身已实现Compareable接口
    3. list.stream().sorted().forEach(System.out::println);// aa dd ff
    4. Student s1 = new Student("aa", 10);
    5. Student s2 = new Student("bb", 20);
    6. Student s3 = new Student("aa", 30);
    7. Student s4 = new Student("dd", 40);
    8. List<Student> studentList = Arrays.asList(s1, s2, s3, s4);
    9. //自定义排序:先按姓名升序,姓名相同则按年龄升序
    10. studentList.stream().sorted(
    11. (o1, o2) -> {
    12. if (o1.getName().equals(o2.getName())) {
    13. return o1.getAge() - o2.getAge();
    14. } else {
    15. return o1.getName().compareTo(o2.getName());
    16. }
    17. }
    18. ).forEach(System.out::println);

    流的中止操作
    1)匹配、聚合操作
    allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
    noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
    anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
    findFirst:返回流中第一个元素
    findAny:返回流中的任意元素
    count:返回流中元素的总个数
    max:返回流中元素最大值
    min:返回流中元素最小值

    1. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    2. boolean allMatch = list.stream().allMatch(e -> e > 10); //false
    3. boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true
    4. boolean anyMatch = list.stream().anyMatch(e -> e > 4); //true
    5. Integer findFirst = list.stream().findFirst().get(); //1
    6. Integer findAny = list.stream().findAny().get(); //1
    7. long count = list.stream().count(); //5
    8. Integer max = list.stream().max(Integer::compareTo).get(); //5
    9. Integer min = list.stream().min(Integer::compareTo).get(); //1

    2)收集操作
    collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。
    Collector 是一个接口,有以下5个抽象方法:
    Supplier supplier():创建一个结果容器A
    BiConsumer accumulator():消费型接口,第一个参数为容器A,第二个参数为流中元素T。
    BinaryOperator
    combiner():函数接口,该参数的作用跟上一个方法(reduce)中的combiner参数一样,将并行流中各 个子进程的运行结果(accumulator函数操作后的容器A)进行合并。
    Function finisher():函数式接口,参数为:容器A,返回类型为:collect方法最终想要的结果R。
    Set characteristics():返回一个不可变的Set集合,用来表明该Collector的特征。有以下三个特征:
    CONCURRENT:表示此收集器支持并发。
    UNORDERED:表示该收集操作不会保留流中元素原有的顺序。
    IDENTITY_FINISH:表示finisher参数只是标识而已,可忽略。

    1. Student s1 = new Student("aa", 10,1);
    2. Student s2 = new Student("bb", 20,2);
    3. Student s3 = new Student("cc", 10,3);
    4. List<Student> list = Arrays.asList(s1, s2, s3);
    5. //装成list
    6. List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10]
    7. //转成set
    8. Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10]
    9. //转成map,注:key不能相同,否则报错或者传入一个function作为key冲突时的解决方案
    10. Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}
    11. //字符串分隔符连接
    12. String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc)
    13. //聚合操作
    14. //1.学生总数
    15. Long count = list.stream().collect(Collectors.counting()); // 3
    16. //2.最大年龄 (最小的minBy同理)
    17. Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20
    18. //3.所有人的年龄
    19. Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40
    20. //4.平均年龄
    21. Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334
    22. // 带上以上所有方法
    23. DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
    24. //分组
    25. Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
    26. //多重分组,先根据类型分再根据年龄分
    27. Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge)));
    28. //分区
    29. //分成两部分,一部分大于10岁,一部分小于等于10岁
    30. Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));

    我们需要熟练掌握
    .map() //抽取某个对象的属性
    .collect(Collectors.groupingBy()) //将对象集合依据某一个对象属性值分组
    .collect(Collectors.toMap()) //将对象集合映射成key-Obj的形式