stream流

1. 简述

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

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

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

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

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

流有三大特点:

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

2. 流的创建

2.1 从数组或集合

  • Arrays.stream(T array) or Stream.of()
  • Collection.stream()
  • Collection.parallelStream()
@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 的特性可以归纳为:

  • 不是数据结构
  • 它也绝不修改自己所封装的底层数据结构的数据。
  • 很容易生成数组或者List
  • 惰性化
    很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要。
  • 并行能力
    当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
  • 可以是无限的
    集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。
    参考
    [1].https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
    [2]. https://www.cnblogs.com/andywithu/p/7404101.html
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容