1、stream的介绍

stream是java8新增的API,搭配同版本的lambda,为我们操作集合、数组提供了很大的便利

stream的作用:
stream将要处理的元素集合看做一个流,然后使用stream API对流中的元素进行排序、筛选、聚合等操作
stream对流的操作分为两种:
  1. 中间操作(intermediate operation),每次返回一个新的流,可以有多个,比如filter()、map()、distinct()、sorted()等
  2. 终端操作(terminal operation),每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值,比如forEach()、collect()、max()、findFirst()等
stream的几个特性:
  1. stream不是一个数据结构也不会存储数据,主要用于集合的逻辑处理
  2. stream不会改变数据源,通常情况下会生成一个新的集合或一个值
  3. stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行

2、stream的创建

stream可以通过集合、数组创建

  1. 通过 java.util.Collection.stream() 方法用集合创建流
List<String> list = new ArrayList<>();
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
  1. 使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,2,3,4,5};
IntStream stream = Arrays.stream(array);
  1. 使用Stream的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(6);

Stream<Double> stream3 = Stream.generate(Math::random).limit(6);
注意
  • parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。简单理解就是多线程异步任务的一种实现

3、stream的使用

使用stream API前,需要先了解Optional类
  • Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方***返回true,调用get()方***返回该对象。
  • Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
  • Optional 类的引入很好的解决空指针异常。

3.1.遍历/筛选/匹配(foreach/filter/find、match)

  • forEach(Consumer<? super T> action):对此流的每个元素执行操作
  • filter(Predicate<? super T> predicate):返回由与此给定谓词匹配的此流的元素组成的流
  • findAny():返回描述流的一些元素的Optional如果流为空,则返回一个空的Optional
  • findFirst():返回描述此流的第一个元素的Optional如果流为空,则返回一个空的Optional
  • allMatch(Predicate<? super T> predicate):返回此流的所有元素是否与提供的谓词匹配
  • anyMatch(Predicate<? super T> predicate):返回此流的任何元素是否与提供的谓词匹配
public class Test {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 5, 4);
        // 遍历集合并筛选大于3的元素
        integers.stream().filter(i -> (i > 3)).forEach(System.out::println);
        // 匹配第一个元素
        Optional<Integer> findFirst = integers.stream().filter(i -> (i > 3)).findFirst();
        // 匹配任意一个元素(适用于并行流)
        Optional<Integer> findAny = integers.parallelStream().filter(i -> (i > 3)).findAny();
        // 是否满足符合条件的元素
        boolean anyMatch = integers.stream().anyMatch(i -> (i > 3));
    }
}

3.2.聚合(max/min/count)

  • max(Comparator<? super T> comparator):根据提供的 Comparator返回此流的最大元素
  • min(Comparator<? super T> comparator):根据提供的 Comparator返回此流的最小元素
  • count():返回此流中的元素数
public class Test {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 5, 4);
        // 自然排序 求最大值
        Optional<Integer> max = integers.stream().max(Integer::compareTo);
        // 自定义排序 求最小值
        Optional<Integer> min = integers.stream().min(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        });
        // 自定义排序 求最小值 lambda语法
        Optional<Integer> min1 = integers.stream().min((o1, o2) -> o1.compareTo(o2));
        // 统计大于3的元素数量
        long count = integers.stream().filter(i -> (i > 3)).count();
    }
}

3.3.映射(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

  • map(Function<? super T,? extends R> mapper):返回由给定函数应用于此流的元素的结果组成的流
  • flatMap(Function<? super T,? extends Stream<? extends R>> mapper):返回由通过将提供的映射函数应用于每个元素而产生的映射流的内容来替换该流的每个元素的结果的流
public class Test {
    public static void main(String[] args) {
        String[] stringArray = {"a", "b", "c"};
        List<String> collect = Arrays.stream(stringArray).map(String::toUpperCase).collect(Collectors.toList());
        System.out.println(collect);

        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 5, 4);
        // 获取每个元素的平方,并使用collect放到一个新的集合中
        List<Integer> collect1 = integers.stream().map(i -> i * i).collect(Collectors.toList());
        System.out.println(collect1);

        List<String> list = Arrays.asList("how are you","I'm fine think you");
        List<String> listNew = list.stream().flatMap(stream -> {
            // 将每个元素转换成一个stream
            String[] split = stream.split(" "); // 通过空格分隔出每个单词
            Stream<String> stream2 = Arrays.stream(split);
            return stream2;
        }).collect(Collectors.toList());
        System.out.println(list);
        System.out.println(listNew);
    }
}

3.4.归约(reduce)

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

常用的三个方法
- reduce(BinaryOperator<T> accumulator):使用 associative累积函数对此流的元素执行 reduction ,并返回描述减小值的 Optional (如果有)
- reduce(T identity, BinaryOperator<T> accumulator):使用提供的身份值和 associative累积功能对此流的元素执行 reduction ,并返回减小的值
- reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner):执行 reduction在此流中的元素,使用所提供的身份,积累和组合功能
public class Test {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 5, 4);
        // 求和方式
        Optional<Integer> sum1 = integers.stream().reduce((x, y) -> x + y);
        Optional<Integer> sum2 = integers.stream().reduce(Integer::sum);
        Integer sum3 = integers.stream().reduce(0, Integer::sum);

        // 求乘积
        Optional<Integer> product = integers.stream().reduce((x, y) -> x * y);

        // 求最大值方式
        Optional<Integer> max1 = integers.stream().reduce((x, y) -> x > y ? x : y);
        Integer max2 = integers.stream().reduce(1, Integer::max);

        System.out.println(sum1.get() + "," + sum2.get() + "," + sum3);
        System.out.println(product.get());
        System.out.println(max1.get() + "," + max2);
    }
}

3.5.收集(collect)

collect主要是使用java.util.stream.Collectors类内置的静态方法

  • collect(Collector<? super T,A,R> collector):使用 Collector对此流的元素执行 mutable reduction Collector
  • collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner):对此流的元素执行 mutable reduction操作

使用的实体类

class Student{
    private String name;
    private int age;
    private String address;
    
    public Student(String name, int age, String address){
        this.name = name;
        this.age = age;
        this.address = address;
    }
    //setter/getter/toString 方法省略
}
3.5.1归集(toList/toSet/toMap)

因为流是不存储数据的,流处理完数据后,需要归集到一个新的集合,比较常用的有以下三种

public class Test {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 5, 4);
        // 筛选大于3的元素并收集到一个新的集合
        List<Integer> collect1 = integers.stream().filter(i -> (i > 3)).collect(Collectors.toList());
        Set<Integer> collect2 = integers.stream().filter(i -> (i > 3)).collect(Collectors.toSet());

        List<Student> students = new ArrayList<>();
        students.add(new Student("zhangsan",22,"A"));
        students.add(new Student("zhangsan",28,"C"));
        students.add(new Student("lisi",19,"B"));
        students.add(new Student("wangwu",22,"C"));
        students.add(new Student("zhaoliu",18,"A"));
        students.add(new Student("***",24,"A"));
        
        /*
         * Collectors.toMap  参数解释
         * Student::getName  key的映射函数
         * s -> s  value的映射函数
         * (s1, s2) -> s1  当key冲突时,调用合并的方法
         */
        //筛选年龄大于20岁的学生 只使用以上两个参数 当出现同名学生是 会报key重复的错误
//        Map<String, Student> collect3 = students.stream()
//                .filter(s -> s.getAge() > 20)
//                .collect(Collectors.toMap(Student::getName, s -> s));
        //筛选年龄大于20岁的学生 如果出现同名学生 则筛选年龄大的
        Map<String, Student> collect4 = students.stream()
                .filter(s -> s.getAge() > 20)
                .collect(Collectors.toMap(Student::getName, s -> s, (s1, s2) -> {
                    if(s1.getAge() > s2.getAge()){
                        return s1;
                    }else{
                        return s2;
                    }
                }));

        System.out.println(collect1);
        System.out.println(collect2);
        //System.out.println(collect3);
        System.out.println(collect4);
    }
}
3.5.2统计(count/averaging)

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

  • 计数:count
  • 平均值:averagingInt、averagingLong、averagingDouble
  • 最值:maxBy、minBy
  • 求和:summingInt、summingLong、summingDouble
  • 统计以上所有:summarizingInt、summarizingLong、summarizingDouble
public class Test {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("zhangsan",18,"A"));
        students.add(new Student("lisi",19,"B"));
        students.add(new Student("wangwu",22,"C"));
        students.add(new Student("zhaoliu",18,"A"));
        students.add(new Student("***",24,"A"));

        // 求学生总数
        Long count = students.stream().collect(Collectors.counting());
        // 求平均年龄
        Double average = students.stream().collect(Collectors.averagingDouble(Student::getAge));
        // 求最高年龄
        Optional<Integer> max = students.stream().map(Student::getAge)
                .collect(Collectors.maxBy(Integer::compare));
        // 求年龄之和
        Integer sum = students.stream().collect(Collectors.summingInt(Student::getAge));
        // 一次性统计所有信息
        DoubleSummaryStatistics collect = students.stream()
                .collect(Collectors.summarizingDouble(Student::getAge));

        System.out.println(count);
        System.out.println(average);
        System.out.println(sum);
        System.out.println(collect);
    }
}
3.5.3分组(partitioningBy/groupingBy)
  • 分区:将stream按条件分为两个Map,比如学生年龄是否大于20岁分为两部分
  • 分组:将集合分为多个Map,比如学生地址分组;有单级分组和多级分组
public class Test {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("zhangsan",18,"A"));
        students.add(new Student("lisi",19,"B"));
        students.add(new Student("wangwu",22,"C"));
        students.add(new Student("zhaoliu",18,"A"));
        students.add(new Student("***",24,"A"));

        // 按学生年龄是否大于20岁分组
        Map<Boolean, List<Student>> partitioningBy = students.stream()
                .collect(Collectors.partitioningBy(s -> s.getAge() > 20));
        // 按学生地址分组
        Map<String, List<Student>> groupingBy = students.stream()
                .collect(Collectors.groupingBy(Student::getAddress));
        // 先按地址分,再按年龄分
        Map<String, Map<Boolean, List<Student>>> collect = students.stream()
                .collect(Collectors.groupingBy(Student::getAddress, Collectors.partitioningBy(s -> s.getAge() > 20)));

        System.out.println(partitioningBy);
        System.out.println(groupingBy);
        System.out.println(collect);
    }
}
3.5.4接合(joining)

joining可以将stream中的元素用指定的内容连接成一个字符串

public class Test {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("zhangsan",18,"A"));
        students.add(new Student("lisi",19,"B"));
        students.add(new Student("wangwu",22,"C"));
        students.add(new Student("zhaoliu",18,"A"));
        students.add(new Student("***",24,"A"));

        // 所有学生姓名通过“,”拼接,也不不写,直接拼接
        String collect = students.stream().map(s -> s.getName()).collect(Collectors.joining(","));
        System.out.println(collect);

    }
}
3.5.5归约(reducing)

Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持

public class Test {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("zhangsan",18,"A"));
        students.add(new Student("lisi",19,"B"));
        students.add(new Student("wangwu",22,"C"));
        students.add(new Student("zhaoliu",18,"A"));
        students.add(new Student("***",24,"A"));

        // 计算10年前学生的年龄总和
        Integer collect = students.stream().collect(Collectors.reducing(0, Student::getAge, (s1, s2) -> (s1 + s2 - 10)));
        System.out.println(collect);

        // 对比stream的reduce方法
        Optional<Integer> reduce = students.stream().map(Student::getAge).reduce(Integer::sum);
        System.out.println(reduce);
        
    }
}

3.6.排序(sorted)

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

  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):Comparator排序器自定义排序
public class Test {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("zhangsan",18,"C"));
        students.add(new Student("lisi",19,"B"));
        students.add(new Student("wangwu",22,"C"));
        students.add(new Student("zhaoliu",18,"A"));
        students.add(new Student("***",24,"A"));

        // 按年龄升序排序(自然排序)
        List<String> collect1 = students.stream()
                .sorted(Comparator.comparing(Student::getAge))
                .map(Student::getName).collect(Collectors.toList());

        // 按年龄降序排序
        List<String> collect2 = students.stream()
                .sorted(Comparator.comparing(Student::getAge).reversed())
                .map(Student::getName).collect(Collectors.toList());

        // 先按年龄在按地址升序排序
        List<String> collect3 = students.stream()
                .sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getAddress))
                .map(Student::getName).collect(Collectors.toList());

        // 按年龄自定义排序(降序)
        List<String> collect4 = students.stream()
                .sorted((s1, s2) -> {
                    return s2.getAge() - s1.getAge();
                }).map(Student::getName).collect(Collectors.toList());
        
        System.out.println(collect1);
        System.out.println(collect2);
        System.out.println(collect3);
        System.out.println(collect4);
    }
}

3.7.其他

流也可以进行合并、去重、限制、跳过等操作

public class Test {
    public static void main(String[] args) {
        String[] array1 = {"a", "b", "c"};
        String[] array2 = {"c", "d", "e"};
        Stream<String> stream1 = Stream.of(array1);
        Stream<String> stream2 = Stream.of(array2);
        // concat-合并 distinct-去重
        List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
        // limit-限制从流中获得前n个数据
        List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
        // skip-跳过前n个数据
        List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
        System.out.println("concat and distinct:" + newList);
        System.out.println("limit:" + collect);
        System.out.println("skip:" + collect2);
    }
}

4、stream的适用场景

4.1 简单代码测试

public class SpeedTest {

    public static void main(String[] args) {
        List<Integer> ints = new LinkedList<Integer>();
        for (int i = 0; i <= 1000000; i++ ) {
            ints.add(i);
        }

        //循环只调用 intValue()
        long start1 = System.currentTimeMillis();
        for (Integer i : ints) {
            i.intValue();
        }
        long end1 = System.currentTimeMillis();
        System.out.println("for loop:"+(end1-start1));

        long start2 = System.currentTimeMillis();
        ints.stream().forEach(i -> {
            i.intValue();
        });
        long end2 = System.currentTimeMillis();
        System.out.println("stream:"+(end2-start2));

        long start3 = System.currentTimeMillis();
        ints.parallelStream().forEach(i -> {
            i.intValue();
        });
        long end3 = System.currentTimeMillis();
        System.out.println("parallelStream:"+(end3-start3));

        /*
        以上代码执行结果:
        for loop:13
        stream:45
        parallelStream:18
         */
         
         System.out.println();

        //循环多执行些内容
        long start4 = System.currentTimeMillis();
        for (Integer i : ints) {
            i.intValue();
            i.longValue();
            i.toString();
            i.byteValue();
            i.floatValue();
            i.doubleValue();
        }
        long end4 = System.currentTimeMillis();
        System.out.println("for loop:"+(end4-start4));

        long start5 = System.currentTimeMillis();
        ints.stream().forEach(i -> {
            i.intValue();
            i.longValue();
            i.toString();
            i.byteValue();
            i.floatValue();
            i.doubleValue();
        });
        long end5 = System.currentTimeMillis();
        System.out.println("stream:"+(end5-start5));

        long start6 = System.currentTimeMillis();
        ints.parallelStream().forEach(i -> {
            i.intValue();
            i.longValue();
            i.toString();
            i.byteValue();
            i.floatValue();
            i.doubleValue();
        });
        long end6 = System.currentTimeMillis();
        System.out.println("parallelStream:"+(end6-start6));

        /*
        以上代码执行结果:
        for loop:78
        stream:45
        parallelStream:30
         */

    }

}

4.2 适用场景

  • 集合超过两个步骤,比如先filter在forEach,使用stream代码会显得更加简洁,效率比普通for循环高
  • 任务较多,注重效能,希望并发执行,可以使用parallelStream
  • 函数式编程

4.3 其他

Lambda、Stream等新特性使得Java函数式编程更为自然、简洁,不过Stream的创建以及传输也有损耗,特别简单的场景可能普通的for循环更为合适。