Java8:Stream概念

参考:Java 8 Stream Tutorial

介绍:什么是流、管道/Pipelinin、惰性/ laziness、部分地构造、流的一次性

1.什么是流

  • 流 Stream<T>是元素集合的描述,而非列举。因而流的元素可以无限。
  • 流 Stream<T>是惰性计算的链表,(高阶函数)filter, map, reduce, find, match, sort等操作,如果不考虑开销,也可以用在Collections上——换言之,这些函数不是什么了不起的东西,而是 Stream<T>的惰性计算,才是要点。
  • 流对象只能够被尾函数消费一次

正则表达式(Regular Expression)是一种生成字符串的字符串,例如

String regEx=”ab*”;

“ab*”——表示了a、ab、abb、abbb……。可以说,正则表达式”ab*”表示了一个无限的集合。

流 Stream<T>表示类型为T的元素的序列,支持对元素的顺序或并发的聚合操作。Java的Collections类似{a、ab、abb、abbb},而流类似”ab*”。

3.5.1  Streams Are Delayed Lists

为Java带来了函数式编程风格的,并非lambda表达式,而是Stream API 。正如SICP所展示的,(高阶函数)filter, map, reduce, find, match, sort等操作,可以建立在list之上(但是开销太大),只有拥有 delayed evaluation /延迟计算/惰性计算的Stream ,才能够发挥上面的高阶函数的威力。

Stream<T>的操作,通常形容为管道/Pipelining,其实,也可以把String的操作称为管道。

        List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1", "c0");
        myList.stream()
                .filter(s -> s.startsWith("c"))
                .map(String::toUpperCase)
                .sorted()
                .forEach(System.out::println);
        boolean b = "a1".toUpperCase().endsWith("1");//比较 流的管道概念。

也可以看一看管道的慢动作:

        Stream<String> stream = myList.stream();
        Stream<String> filter = stream.filter(s -> s.startsWith("c"));
        Stream<String> map = filter.map(String::toUpperCase);
        Stream<String> sorted = map.sorted();
        sorted.forEach(System.out::println);

上面代码说明了,流的操作分为两种
Intermediate /中间的函数:程序中可以多次地、串接地调用流的 intermediate 操作,因为中间操作如映射map、过滤filter函数返回一个流对象。这类操作具有惰性/ laziness。虽然代码写出了对它们 的调用,但是并没有真正执行。

  • Stream<T> filter(Predicate<? super T> predicate);
  • <R> Stream<R> map(Function<? super T, ? extends R> mapper);
  • Stream<T> distinct();
  • Stream<T> sorted();
  • Stream<T> peek(Consumer<? super T> action);
  • Stream<T> limit(long maxSize);
  • Stream<T> skip(long n);
  • 一些创建Stream<T>的方法,Stream.iterategenerate,作为第一个调用。

Terminal /结尾的函数:对于流的串接调用,只能在最后加一个 terminal函数,正如调用String的 .endsWith(“1”)。结尾函数返回一个值或void函数产生一个副作用/side effect。 terminal函数的一个作用,在于唤醒前面的懒虫。

  • void forEach(Consumer<? super T> action);
  • Object[] toArray(); //重载
  • Optional<T> reduce(BinaryOperator<T> accumulator);  //重载
  • <R, A> R collect(Collector<? super T, A, R> collector);
  • long count();
  • boolean anyMatch(Predicate<? super T> predicate);//相关
  • Optional<T> findFirst();//Optional<T> findAny();

延迟计算+管道

流的一个重要特点:流总是部分地构造,并将该部分交给后面的函数处理;如果函数需要更多的数据,流会自动地构造新的数据。换言之,流像挤牙膏一样,一个个元素交给处理函数,给程序员的印象则是整个流的元素都存在。

为了观察流的处理管道,下面在处理函数中添加输出语句。

1.懒虫

    public static void m2() {
        Stream<String> of = Stream.of("c2", "a1",  "b0");
        Stream<String> map = of.map(s -> {
                    System.out.println("map: " + s);//副作用
                    return s.toUpperCase();
                });      
    }

执行m2()的代码,没有如何输出。执行完m2()后流对象of成为垃圾,of.map()并没有真正执行,因为没有尾函数唤醒中间函数map。
2.尾函数

    public static void m2() {
        Stream<String> of //同上
        System.out.println(map);
        System.out.println(map.count());
    }

添加尾函数count(),则输出: java.util.stream.ReferencePipeline$3@31befd9f 

map: c2

map: a1

map: b0

3
3.部分地构造

如果添加的结尾函数为 forEach,注意:流对象of有3个元素,但是流对象map在懒惰中,开始的时候没有元素。

 map.forEach()说:”map给我数据”;

流对象map从流对象of中提取一个数据”c2″执行of.map();这时流对象map有了一个元素”C2″;

forEach处理”C2″;但是,map.forEach()要处理流对象map的所有元素,什么是所有?按照约定,of.map()将of对象的所有元素转换/map()后,形成流对象map的元素,因此,map的所有元素,个数即of对象的所有元素的个数。

    public static void m2() {
        Stream<String> of = Stream.of("c2", "a1", "b0");
        Stream<String> map = of.map(s -> {
            System.out.println("map: " + s);//副作用
            return s.toUpperCase();
        });
        //System.out.println(map);
        //System.out.println(map.count());
         map.forEach(s -> System.out.println("forEach: " + s)); 
    }

于是,输出为:

map: c2

forEach: C2

map: a1

forEach: A1

map: b0

forEach: B0

练习:请解释下面代码的输出:(结尾函数找流对象filter,而流对象filter找流对象map 要数据)

    public static void m2() {
        Stream<String> of = Stream.of("c2", "a1","a2", "b0");
        Stream<String> map = of.map(s -> {
            System.out.println("map: " + s);//副作用
            return s.toUpperCase();
        });
        Stream<String> filter = map.filter(s -> {
            System.out.println("filter: " + s);
            return s.startsWith("A");
        });
        filter.forEach(s -> System.out.println("forEach: " + s));
    }

输出:

map: c2
filter: C2  //流对象filter没有”C2″这个元素
map: a1
filter: A1
forEach: A1
map: a2
filter: A2
forEach: A2
map: b0
filter: B0

ok,如果我们交换上面代码中map()和filter()函数的调用顺序,先filter()再map()(代码见下面),可以减少函数执行的次数

流的一次性

    public static void m3() {
        Stream<String> map = Stream.of("c2", "a1", "a2", "b0")                
                .filter(s -> s.startsWith("A"))
                .map(s -> s.toUpperCase());
        map.forEach(s -> System.out.println("forEach: " + s));        
        long count = map.count();//运行时异常
    }

假设我们在打印流对象map的元素的同时,还要统计其个数,m3()代码抛出异常

java.lang.IllegalStateException: stream has already been operated upon or closed

因为,流对象只能够被尾函数消费一次,或者说,一个流对象只能够调用一次结尾函数。那么如何对一个流完成多次尾操作呢?

//Stream<String> map2 = map; //想都不用想

解决方案有:

  • collect 或toArray,再流化。
    public static void m3() {
        String[] toArray = (String[])(Stream.of("c2", "a1", "a2", "b0")                
                .filter(s -> s.startsWith("A"))
                .map(s -> s.toUpperCase())
                .toArray());
        Stream.of(toArray)
                .forEach(s -> System.out.println("forEach: " + s));
        long count = Stream.of(toArray)
                .count();          
    }
  • stream supplier创建两个流
    public static void m3() {
        Supplier<Stream<String>> streamSupplier
                = () -> Stream.of("c2", "a1", "a2", "b0")
                .filter(s -> s.startsWith("A"))
                .map(s -> s.toUpperCase());

        streamSupplier.get().forEach(s -> System.out.println("forEach: " + s));
        streamSupplier.get().count();
    }

    原文作者:yqj2065
    原文地址: https://blog.csdn.net/yqj2065/article/details/51686344
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞