stream流

2019-05-26  本文已影响0人  扎Zn了老Fe

1. 简述

流是有关算法和计算的,允许你以声明性方式处理数据集合,可以看成遍历数据集的的高级迭代器。

此外,和迭代器不同,流还可以并行处理。数据可以分成多段,其中每一个都在不同的线程中执行,最后将结果一起输出。

当我们使用一个流的时候,通常包括三个基本步骤:

获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。

图 1. 流管道 (Stream Pipeline) 的构成

流有三大特点:

  1. 流不储存元素;
  2. 流不会修改其数据源;
  3. 流执行具有延迟特性;

2. 流的创建

2.1 从数组或集合

@Test
    public void testArrayStream() {
        //1.通过Arrays.stream
        //1.1基本类型
        Integer[] arr = new Integer[]{1,2,3,4,5,6,7};

        //通过Arrays.stream
        Stream stream1 = Arrays.stream(arr);

        stream1.forEach(System.out::print);

        //2.通过Stream.of
        Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6,7);

        stream2.forEach(System.out::print);
    }

    @Test
    public void testCollectionStream(){
        List<Integer> strs = Arrays.asList(1,2,3,4,5,6,7);
        //创建普通流
        Stream<Integer> stream  = strs.stream();
        //创建并行流
        Stream<Integer> stream1 = strs.parallelStream();
    }

2.2 创建无限流

@Test
    public void testUnlimitStream() {

        Random seed = new Random();
        Supplier<Integer> random = seed::nextInt;
        Stream.generate(random).limit(10).forEach(System.out::println);

        //Another way
        IntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(10).forEach(System.out::println);
    }

2.3 创建规律流

    @Test
    public void testUnlimitStream1(){
        Stream.iterate(0,x -> x+1).limit(10).forEach(System.out::println);
        Stream.iterate(0,x -> x).limit(10).forEach(System.out::println);
    }

3. 流的操作

流操作分为中间操作和终端操作,中间操作会返回另外一个流,可以继续执行下一个中间操作。
终端操作是返回结果,终端操作触发流执行中间操作,终端操作,到此流的生命结束。

3.1 中间操作

filter
filter对原始Stream进行过滤,符合条件的原数被留下来生成新的流。

@Test
    public void testFilter() {
        Integer[] sixNums = {1, 2, 3, 4, 5, 6};
        Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
    }

sorted
sorted对原始Stream中的元素进行排序,排序后生成新的流。

  Integer[] sixNums = {1, 2, 4, 3, 5, 6};
        Integer[] orderedNums = Stream.of(sixNums).sorted(Comparator.comparing(x -> x)).toArray(Integer[]::new);

distinct
去掉重复值

@Test
    public void testDistinct() {
        Integer[] sixNums = {1, 2, 4, 3, 5, 5, 6};
        Integer[] distinctedNums = Stream.of(sixNums).distinct().toArray(Integer[]::new);
        System.out.println(distinctedNums);
    }

map
map将现有流转换为新的流。map接收一个函数作为参数,该函数作用于流中的每一个元素,并将其映射成一个新的元素。

@Test
public void testMap() {
    String[] arr = new String[]{"yes", "YES", "no", "NO"};
    Arrays.stream(arr).map(x -> x.toLowerCase()).forEach(System.out::println);
}

flatMap
将流扁平化,流中的每一个元素都被拆解成一个新的流。使用flatMap需要提前知道原来的流中的元素类型。

@Test
    public void testFlapMap1() {
        String[] words = {"Hello", "World"};
        Stream.of(words)
                .map(word -> word.split(""))
                .flatMap(Arrays::stream).forEach(System.out::println);
    }

limit
截断流,该方法返回一个不超过给定长度的流。

3.2 终端操作

遍历流中的每一个元素。当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为。

@Test
    public void testFlapMap1() {
        String[] words = {"Hello", "World"};
        Stream.of(words)
                .map(word -> word.split(""))
                .flatMap(Arrays::stream).forEach(System.out::println);
    }

findFirst, findAny

两者功能类似,findAny不要求顺序,使用并行流有优势。
@Test
    public void testFindFirser() {
        String[] arr = new String[]{"yes", "YES", "no", "NO"};
        Arrays.stream(arr).filter(str -> Objects.equals(str, "yes")).findFirst().ifPresent(System.out::println);
    }

allMatch, noneMatch, anyMatch
Stream提供了三个match方法,allMatch要求流中所有元素满足条件才返回true, anyMatch中只要有一个元素符合就返回true, noneMatch与allMatch相反,所有元素都不符合,返回true

@Test
    public void testMatch() {
        String[] arr = new String[]{"yes", "YES", "no", "NO"};
        System.out.println(Arrays.stream(arr).noneMatch( str  -> str.length() > 2));
        System.out.println(Arrays.stream(arr).anyMatch( str  -> str.length() > 2));
        System.out.println(Arrays.stream(arr).allMatch( str  -> str.length() > 2));
    }

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

Integer sum = integers.reduce(0, (a, b) -> a+b); 

Integer sum = integers.reduce(0, Integer::sum);
@Test
    public void testReduce() {
        // 字符串连接,concat = "ABCD"
        String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
        // 求最小值,minValue = -3.0
        double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
        // 求和,sumValue = 10, 有起始值
        int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
        // 求和,sumValue = 10, 无起始值
        sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
        // 过滤,字符串连接,concat = "ace"
        concat = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);
    }

count
计算流中元素数量。
collect
这个在实际项目中使用非常多,通过collect生成结果。可以生成各种形式,Array, List, Set, Map。另外,还能进行分组。

public class Student {
        private String name;
        private Integer score;
        //-----getters and setters-----

        Student(String name, Integer score) {
            this.name = name;
            this.score = score;
        }

        public String getName() {
            return name;
        }

        public Integer getScore() {
            return score;
        }

        public String toString() {
            return "name: " + name
                    + " score: " + score;
        }
    }

    Student[] students;

    @Before
    public void init(){
        students = new Student[10];
        for (int i = 0; i <= 3; i++){
            Student student = new Student("user", i);
            students[i] = student;
        }
        for (int i = 3; i <= 6; i++){
            Student student = new Student("user" + i, i + 1);
            students[i] = student;
        }
        for (int i = 6; i < 10; i++){
            Student student = new Student("user" + i, i + 2);
            students[i] = student;
        }

    }
    @Test
    public void testCollect1(){
        /**
         * 生成List
         */
        List<Student> list = Arrays.stream(students).collect(Collectors.toList());
        list.forEach((x) -> System.out.println(x));
        /**
         * 生成Set
         */
        Set<Student> set = Arrays.stream(students).collect(Collectors.toSet());
        set.forEach((x)-> System.out.println(x));
        /**
         * 生成Map,如果包含相同的key,则需要提供第三个参数,否则报错, 这里用了覆盖旧value
         */
        Map<String,Integer> map = Arrays.stream(students).collect(Collectors.toMap(Student::getName, Student::getScore, (v1, v2) -> v2));
        map.forEach((x, y) -> System.out.println(x + "->" + y));
    }

    /**
     * 生成数组
     */
    @Test
    public void testCollect2(){
        Student[] s = Arrays.stream(students).toArray(Student[]::new);
        for (int i=0; i< s.length; i++)
            System.out.println(s[i]);
    }

groupingBy
groupingBy能够将流按照某个条件进行进行分组和分片,条件为key,分组之后的元素组成新的流,对新的流可以进行中间操作和终端操作,默认生成List。最后生成Map的类型,key就是分组条件,value取决于对分组后的流进行操作后生成的类型。生成Map会有重复key的问题,使用groupingBy将重复key的value值放到一个List, 同样能够解决此问题。

如果想要按照多个条件进行分组,一种方法是groupingB嵌套使用groupBy,多次进行分组;另一种方法是按照多个分组条件进行拼接成一个key,按照这个拼接后的key进行分组。

@Test
    public void testGroupBy1(){
        Map<String,List<Student>> map = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName));
        map.forEach((x,y)-> System.out.println(x+"->"+y));
    }

    /**
     * 如果只有两类,使用partitioningBy会比groupingBy更有效率
     */
    @Test
    public void testPartitioningBy(){
        Map<Boolean,List<Student>> map = Arrays.stream(students).collect(Collectors.partitioningBy(x -> x.getScore() > 5));
        map.forEach((x, y)-> System.out.println(x+ "->" + y));
    }

    /**
     * downstream指定类型
     */
    @Test
    public void testGroupBy2(){
        Map<String,Set<Student>> map = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.toSet()));
        map.forEach((x, y)-> System.out.println(x + "->"+ y));
    }

    /**
     * downstream 聚合操作
     */
    @Test
    public void testGroupBy3(){
        /**
         * counting
         */
        Map<String,Long> map1 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
        map1.forEach((x, y)-> System.out.println(x + "->" + y));
        /**
         * summingInt
         */
        Map<String,Integer> map2 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.summingInt(Student::getScore)));
        map2.forEach((x,y) -> System.out.println(x + "->" + y));
        /**
         * maxBy
         */
        Map<String,Optional<Student>> map3 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.maxBy(Comparator.comparing(Student::getScore))));
        map3.forEach((x, y)-> System.out.println(x + "->" + y));
        
        /**
         * mapping, 这种也用的比较多,可以用元素的变量作为value, 也可以创建新的对象作为value
         */
        Map<String,Set<Integer>> map4 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.mapping(Student::getScore, Collectors.toSet())));
        map4.forEach((x, y)-> System.out.println(x + "->" + y));

        /**
         * mapping, 使用元素多个变量进行分组
         */
        Map<String, Map<Integer, List<Student>>> map5 = Arrays.stream(students).collect(Collectors.groupingBy(Student::getName, Collectors.groupingBy(Student::getScore)));
        map5.forEach((x, y)-> System.out.println(x + "->" + y));

        /**
         * mapping, 使用元素多个变量进行分组, 第二种方法,将多个变量进行拼接, 个人推荐使用这种
         */
        Map<String, List<Student>> map6 = Arrays.stream(students).collect(Collectors.groupingBy(student -> student.getName() + "_" + student.getScore()));
        map6.forEach((x, y)-> System.out.println(x + "->" + y));
    }

总结

总之,Stream 的特性可以归纳为:

上一篇下一篇

猜你喜欢

热点阅读