Java 8 之 Stream 的创建

Stream

Java 8 中新增的特性旨在帮助程序员写出更好的代码,其中对核心类库的改进主要包括集合类的 API 和 新引入的流(Stream)。Stream 使程序员可以站在更高的抽象层次上对集合进行操作。


Stream 实现机制

外部迭代

Java 8 之前,如果要操作一个集合中的元素,就需要遍历集合中元素

        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        int count = 0;
        for (Integer integer : list) {
            if (integer > 1) {
                count++;
            }
        }

这个代码得到了 list 中的大于 1 的元素的个数,其中使用了 foreach 语法进行了遍历,而 foreach 语法是利用了 Iterator 对象来控制的迭代过程,很明显这个是一个外部迭代,只是进行了封装了一层而已。

内部迭代

把上面的代码用流实现

        long count = Stream.of(1, 2, 3)
                .filter(integer -> integer > 1)
                .count();

Stream.of(1,2,3) 把 List 转化为了 Stream,然后在 Stream 的内部进行过滤(filter()) 和 求和(count()) 操作,这样就把外部迭代转化为了内部迭代。

Stream 特性

lazy

Stream 是惰性的(lazy),就如同 RxJava 中的 lazy Observable 一样。

        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        Stream<Integer> filterStream = integerStream.filter(integer -> {
            Log.d(TAG, "integer = " + integer);
            return integer > 1;
        });

如果运行这段代码,不会有任何 Log 出现,那么 Stream.filter() 有什么作用呢? Stream.filter() 就如同设计模式中的装饰模式一样,它只是对 Stream 进行装饰,这里的装饰指的就是过滤。

像 Steam.filter() 把一个 Stream 转化为另外一个 Stream 的方法,我称之为 过渡操作符(intermediate operations)。
而如果想要 Stream 运作起来,需要 Steam 中的 终止操作符( terminal operation ),例如 Stream.count();

        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        Stream<Integer> filterStream = integerStream.filter(integer -> {
            Log.d(TAG, "integer = " + integer);
            return integer > 1;
        });
        long count = filterStream.count();

运行这段代码就可以看到 Log 的出现。

一次性

Stream 只能在操作符( Stream 的方法)上执行一次,例如

        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        integerStream.map(integer -> "s");
        integerStream.filter(integer -> integer > 1);

虽然 map 和 filter 都是过渡操作符,但是运行程序后,会报错

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

所以一个流只能执行一次操作。

流的有序性

Stream 如同 RxJava 的 Observable 一样,发射的元素都是有序的

        List<Integer> collect = Stream.of(1, 2, 3, 4, 5)
                .map(integer -> {
                    Log.d(TAG, "step1: " + integer);
                    return integer;
                })
                .map(integer -> {
                    Log.d(TAG, "step2: " + integer);
                    return integer;
                })
                .collect(Collectors.toList());

因为有 及早求值方法 Stream.collect(),所以可以看到 Log 信息

D/david: step1: 1
D/david: step2: 1
D/david: step1: 2
D/david: step2: 2
D/david: step1: 3
D/david: step2: 3
D/david: step1: 4
D/david: step2: 4
D/david: step1: 5
D/david: step2: 5

Log 中很有规律,元素 1 先走完两个 map,然后才是元素 2 走完两个 map,然后是元素 3,4,5。这就如同排队买票一样,只有前面一个人买完了票,后面一个人才能来买票。这就是流的顺序性。

可关闭性

Stream 有一个 close() 方法,并且实现了 AutoCloaseable 接口。但是几乎所有的 Stream 都不需要在使用后关闭。一般来说,只有 Stream 来源于 IO 管道的,需要手动关闭。

顺序执行和并行执行

Stream 可以顺序执行,也可以并行执行。
Stream 也可以由创建的方式来决定顺序执行还是并行执行。例如,list.stream() 创建一个顺序执行的 Stream,而 list.parallelStream() 创建一个并行执行的 Stream。 然而执行的方式是可以通过 Stream.parallel() 和 Stream.sequential() 来改变的。
既然可以改变流的执行方法,自然也可以查询流的执行方式,Stream.isParallel() 方法查询是否并行的流。


创建 Stream

Stream.of()

    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

Stream.of() 以类型为 T 的可变长数组 values 作为参数,创建 Stream 对象,例如

Stream<Integer> integerStream = Stream.of(1, 2, 3);

其中需要注意的是,传入 Stream.of() 参数的类型决定了 Stream 的泛型。那么如果把 List< Integer > 作为参数传入,返回的 Stream 就是 Stream< List< Integer > >

        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Stream<List<Integer>> listStream = Stream.of(list);

Stream.generate()

Stream.generate(Supplier s) 生成一个无线的流

        Stream<Integer> integerStream = Stream.generate(new Supplier<Integer>() {
            int i = 0;

            @Override
            public Integer get() {
                return i++;
            }
        });

如果我们用一个 Stream 的 终止操作符 就可以看到无限个元素,例如用 Stream.forEach()

        Stream.generate(new Supplier<Integer>() {
            int i = 0;

            @Override
            public Integer get() {
                return i++;
            }
        }).forEach(integer -> Log.d(TAG, "integer = " + integer));

运行后,就可以看到茫茫多的 Log 信息。

在实际中可能只需要这个无限流中的前10,那么可以用 Stream.limite(10) 来限制流中元素的个数

        Stream.generate(new Supplier<Integer>() {
            int i = 0;

            @Override
            public Integer get() {
                return i++;
            }
        }).limit(10).forEach(integer -> Log.d(TAG, "integer = " + integer));

再运行代码,就只能看到前10个元素了。

Stream.iterate()

Stream.iterate() 方法用代码比较好解释,这个方法生成的流的关键代码是 Iterator 中的 next() 方法

    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        // ...
        final Iterator<T> iterator = new Iterator<T>() {
            @SuppressWarnings("unchecked")
            T t = (T) Streams.NONE;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            public T next() {
                // 生成 Stream 中的元素
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        // 用 iterator 创建 Stream
        // ...
    }

Stream.iterate() 产生的 Stream 的第一个元素是 seed, 后续的元素是 f(seed), f(f(seed)), f(f(f(seed))) … , 例如

        Stream.iterate(1, integer -> ++integer)
                .limit(3)
                .forEach(integer -> Log.d(TAG, "integer = " + integer));

产生的结果就是1,2,3 ,也就是对应的 1 , f(1), f(f(1))。

Stream.Builder.build()

以 Builder 模式创建 Stream

        Stream.Builder<String> builder = Stream.builder();
        builder.add("hello");
        builder.accept("world");
        Stream<String> stream = builder.build();
        stream.forEach(s -> Log.d(TAG, "s = " + s));

add() 和 accept() 从实现上是等效的,只不过 add() 是 default 方法。

Stream.concat()

Stream.concat(Stream< ? extends T > a, Stream< ? extends T > b),把 Stream b 连接到 Stream a 之后,a 和 b 中元素的顺序不变。

Stream.empty()

创建一个空的 Stream,目前我还不知道这个到底怎么用。

集合创建Stream

        List<Integer> list = Arrays.asList(1, 2, 3);
        Stream<Integer> stream = list.stream();

数组创建Stream

        int[] ints = new int[]{1, 3, 4};
        IntStream stream1 = Arrays.stream(ints);

map 类不能创建 Stream,为何? 因为 map 类有 key 和 value 两个值,除非把 key, value 转化为一个对象

发布了50 篇原创文章 · 获赞 30 · 访问量 400万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览