美文网首页Java8
Java8 Stream流

Java8 Stream流

作者: 超天大圣JR | 来源:发表于2019-12-17 12:27 被阅读0次

    一、Stream流的特性

    • Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
    • 这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
    • Stream不保存数据,故每个Stream流只能使用一次。

    关于应用在Stream流上的操作,可以分成两种:Intermediate(中间操作)和Terminal(终止操作)。中间操作的返回结果都是Stream,故可以多个中间操作叠加;终止操作用于返回我们最终需要的数据,只能有一个终止操作。

    二、Stream的操作分类

    Stream的操作有Intermediate、Terminal和Short-circuiting:

    • Intermediate(中间操作):map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
    • Terminal(终止操作):forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
    • Short-circuiting(短路):anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    三、惰性求值和及早求值方法

    像filter这样只描述Stream,最终不产生新集合的方法叫作惰性求值方法;而像count这样最终会从Stream产生值的方法叫作及早求值方法。

    long count = allArtists.stream()
        .filter(artist -> {
            System.out.println(artist.getName());
                return artist.isFrom("London");
            })
        .count();
    

    如何判断一个操作是惰性求值还是及早求值,其实很简单,只需要看其返回值即可:如果返回值是Stream,那么就是惰性求值;如果返回值不是Stream或者是void,那么就是及早求值。上面的示例中,只是包含两步:一个惰性求值filter和一个及早求值count。
    在一个Stream操作中,可以有多次惰性求值,但有且仅有一次及早求值。

    四、对Stream的操作一般可以归纳为3个部分

    1. 创建Stream

    • 静态工厂方法
      of方法,其生成的Stream是有限长度的,Stream的长度为其内的元素个数。
    - of(T... values):返回含有多个T元素的Stream
    - of(T t):返回含有一个T元素的Stream
    示例:
    Stream<Integer> integerStream = Stream.of(1, 2, 3);
    Stream<String> stringStream = Stream.of("A");
    
    • generator方法,返回一个无限长度的Stream,其元素由Supplier接口的提供。
    - generate(Supplier<T> s):返回一个无限长度的Stream
    示例:
    Stream<Double> generateA = Stream.generate(new Supplier<Double>() {
        @Override
        public Double get() {
            return java.lang.Math.random();
        }
    });
    
    Stream<Double> generateB = Stream.generate(()-> java.lang.Math.random());
    Stream<Double> generateC = Stream.generate(java.lang.Math::random);
    

    以上三种形式达到的效果是一样的,只不过是下面的两个采用了Lambda表达式,简化了代码,其实际效果就是返回一个随机值。一般无限长度的Stream会与filter、limit等配合使用,否则Stream会无限制的执行下去。

    • iterate方法,其返回的也是一个无限长度的Stream,与generate方法不同的是,其是通过函数f迭代对给指定的元素种子而产生无限连续有序Stream,其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环。
    - iterate(T seed, UnaryOperator<T> f)
    示例:
    Stream.iterate(1, item -> item + 1)
            .limit(10)
            .forEach(System.out::println); 
            // 打印结果:1,2,3,4,5,6,7,8,9,10
    

    上面示例,种子为1,也可认为该Stream的第一个元素,通过f函数来产生第二个元素。接着,第二个元素,作为产生第三个元素的种子,从而产生了第三个元素,以此类推下去。需要主要的是,该Stream也是无限长度的,应该使用filter、limit等来截取Stream,否则会一直循环下去。

    • empty方法返回一个空的顺序Stream,该Stream里面不包含元素项。

    • Collection接口和数组的默认方法
      在Collection接口中,定义了一个默认方法stream(),用来生成一个Stream。

        public interface Collection<E> extends Iterable<E> {
            ***
            default Stream<E> stream() {
                return StreamSupport.stream(spliterator(), false);
            }
            ***
        }
    

    在Arrays类,封装了一些列的Stream方法,不仅针对于任何类型的元素采用了泛型,更对于基本类型作了相应的封装,以便提升Stream的处理效率。

    public class Arrays {
        ***
        public static <T> Stream<T> stream(T[] array) {
            return stream(array, 0, array.length);
        }
    
       public static LongStream stream(long[] array) {
            return stream(array, 0, array.length);
        }
        ***
    }
    示例:
    int ids[] = new int[]{1, 2, 3, 4};
    Arrays.stream(ids)
            .forEach(System.out::println);
    
    • 其他
      Random.ints()
      BitSet.stream()
      Pattern.splitAsStream(java.lang.CharSequence)
      JarFile.stream()

    2. Intermediate:通过一系列中间(Intermediate)方法,对数据集进行过滤、检索等数据集的再次处理,实际上是将源Stream转换为一个新的Stream,以达到需求效果。

    • concat
      concat方法将两个Stream连接在一起,合成一个Stream。若两个输入的Stream都是排序的,则新Stream也是排序的;若输入的Stream中任何一个是并行的,则新的Stream也是并行的;若关闭新的Stream时,原两个输入的Stream都将执行关闭处理。
    Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5))
           .forEach(integer -> System.out.print(integer + "  "));
    // 打印结果:  1  2  3  4  5  
    
    • distinct
      distinct方法以达到去除掉原Stream中重复的元素,生成的新Stream中没有重复的元素。
    Stream.of(1,2,3,1,2,3)
            .distinct()
            .forEach(System.out::println); 
    // 打印结果:1,2,3
    
    • filter
      filter方法对原Stream按照指定条件过滤,在新建的Stream中,只包含满足条件的元素,将不满足条件的元素过滤掉。
    Stream.of(1, 2, 3, 4, 5)
            .filter(item -> item > 3)
            .forEach(System.out::println);
    // 打印结果:4,5
    

    \color{red}{filter传入的Lambda表达式必须是Predicate实例,参数可以为任意类型,而其返回值必须是boolean类型。}

    • map
      map方法将对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。其实很好理解,如果想将原Stream中的数据类型,转换为double,int或者是long是可以调用相对应的方法。
    Stream.of("a", "b", "hello")
            .map(item-> item.toUpperCase())     // 这里与flatMap不同
            .forEach(System.out::println);
            // 打印结果
            // A, B, HELLO
    

    \color{red}{map传入的Lambda表达式必须是Function实例,参数可以为任意类型,而其返回值也是任性类型,javac会根据实际情景自行推断。}

    • flatMap
      flatMap方法与map方法类似,都是将原Stream中的每一个元素通过转换函数转换不同的是,该换转函数的对象是一个Stream,也不会再创建一个新的Stream,而是将原Stream的元素取代为转换的Stream。如果转换函数生产的Stream为null,应由空Stream取代。flatMap有三个对于原始类型的变种方法,分别是:flatMapToInt,flatMapToLong和flatMapToDouble。
    Stream.of(1, 2, 3)
        .flatMap(integer -> Stream.of(integer * 10))   //这里与map不同
        .forEach(System.out::println);
        // 打印结果
        // 10,20,30
    

    传给flatMap中的表达式接受了一个Integer类型的参数,通过转换函数,将原元素乘以10后,生成一个只有该元素的流,该流取代原流中的元素。
    \color{red}{flatMap传入的Lambda表达式必须是Function实例,参数可以为任意类型,而其返回值类型必须是一个Stream。}

    • peek
      peek方法生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行
    Stream.of(1, 2, 3, 4, 5)
            .peek(integer -> System.out.println("accept:" + integer))
            .forEach(System.out::println);
    // 打印结果
    // accept:1
    //  1
    //  accept:2
    //  2
    //  accept:3
    //  3
    //  accept:4
    //  4
    //  accept:5
    //  5
    
    • skip
      skip方法将过滤掉原Stream中的前N个元素,返回剩下的元素所组成的新Stream。如果原Stream的元素个数大于N,将返回原Stream的后(原Stream长度-N)个元素所组成的新Stream;如果原Stream的元素个数小于或等于N,将返回一个空Stream。
    Stream.of(1, 2, 3,4,5)
    .skip(2)
    .forEach(System.out::println);
    // 打印结果
    // 3,4,5
    
    • sorted
      sorted方法将对原Stream进行排序,返回一个有序列的新Stream。sorterd有两种变体sorted(),sorted(Comparator),前者将默认使用Object.equals(Object)进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。
    Stream.of(5, 4, 3, 2, 1)
            .sorted()
            .forEach(System.out::println);
            // 打印结果
            // 1,2,3,4,5
    
    Stream.of(1, 2, 3, 4, 5)
            .sorted((o1, o2) -> o2 - o1)    //逆序
            .forEach(System.out::println);
            // 打印结果
            // 5, 4, 3, 2, 1
    

    3. Terminal通过最终(terminal)方法完成对数据集中元素的处理。

    long count = Stream.of(1, 2, 3, 4, 5)
            .count();
    System.out.println("count:" + count);// 打印结果:count:5
    
    • forEach
      forEach方法前面已经用了好多次,其用于遍历Stream中的所元素,避免了使用for循环,让代码更简洁,逻辑更清晰。
    Stream.of(5, 4, 3, 2, 1)
        .sorted()
        .forEach(System.out::println);
        // 打印结果
        // 1,2,3,4,5
    
    • forEachOrdered
      forEachOrdered方法与forEach类似,都是遍历Stream中的所有元素,不同的是,如果该Stream预先设定了顺序,会按照预先设定的顺序执行(Stream是无序的),默认为元素插入的顺序。
    Stream.of(5,2,1,4,3)
            .forEachOrdered(integer -> {
                System.out.println("integer:"+integer);
            }); 
            // 打印结果
            // integer:5
            // integer:2
            // integer:1
            // integer:4
            // integer:3
    
    • max
      max方法根据指定的Comparator,返回一个Optional,该Optional中的value值就是Stream中最大的元素。

    原Stream根据比较器Comparator,进行排序(升序或者是降序),所谓的最大值就是从新进行排序的,max就是取重新排序后的最后一个值,而min取排序后的第一个值。

    Optional<Integer> max = Stream.of(1, 2, 3, 4, 5)
            .max((o1, o2) -> o2 - o1);
    System.out.println("max:" + max.get());// 打印结果:max:1
    
    • min
      min方法根据指定的Comparator,返回一个Optional,该Optional中的value值就是Stream中最小的元素。
    Optional<Integer> max = Stream.of(1, 2, 3, 4, 5)
            .max((o1, o2) -> o1 - o2);
    System.out.println("max:" + max.get());// 打印结果:min:5
    

    五、Short-circuiting操作

    • allMatch
      allMatch操作用于判断Stream中的元素是否全部满足指定条件。如果全部满足条件返回true,否则返回false。
    boolean allMatch = Stream.of(1, 2, 3, 4)
        .allMatch(integer -> integer > 0);
    System.out.println("allMatch: " + allMatch); // 打印结果:allMatch: true 
    
    • findAny
      findAny操作用于获取含有Stream中的某个元素的Optional,如果Stream为空,则返回一个空的Optional。由于此操作的行动是不确定的,其会自由的选择Stream中的任何元素。在并行操作中,在同一个Stream中多次调用,可能会不同的结果。在串行调用时,Debug了几次,发现每次都是获取的第一个元素,个人感觉在串行调用时,应该默认的是获取第一个元素。
    Optional<Integer> any = Stream.of(1, 2, 3, 4).findAny();
     System.out.println(any.get());
    //输出结果:1
    
    • findFirst
      findFirst操作用于获取含有Stream中的第一个元素的Optional,如果Stream为空,则返回一个空的Optional。若Stream并未排序,可能返回含有Stream中任意元素的Optional。
    Optional<Integer> any = Stream.of(1, 2, 3, 4).findFirst();
    
    • limit
      limit方法将截取原Stream,截取后Stream的最大长度不能超过指定值N。如果原Stream的元素个数大于N,将截取原Stream的前N个元素;如果原Stream的元素个数小于或等于N,将截取原Stream中的所有元素。
    Stream.of(1, 2, 3,4,5)
            .limit(2)
            .forEach(System.out::println);
            // 打印结果
            // 1,2
    

    传入limit的值为2,也就是说被截取后的Stream的最大长度为2,又由于原Stream中有5个元素,所以将截取原Stream中的前2个元素,生成一个新的Stream。

    • noneMatch
      noneMatch方法将判断Stream中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回true;否则,返回false。
        boolean noneMatch = Stream.of(1, 2, 3, 4, 5)
            .noneMatch(integer -> integer > 10);
        System.out.println("noneMatch:" + noneMatch); // 打印结果 noneMatch:true
    
        boolean noneMatch_ = Stream.of(1, 2, 3, 4, 5)
                .noneMatch(integer -> integer < 3);
        System.out.println("noneMatch_:" + noneMatch_); // 打印结果 noneMatch_:false
    

    相关文章

      网友评论

        本文标题:Java8 Stream流

        本文链接:https://www.haomeiwen.com/subject/msxdgctx.html