浅谈java8中的流

Posted on Aug 18, 2021

万物皆流,无物常驻

导引

过程的抽象

流(stream)是在java8中出现的一种新的数据抽象,它对数据的处理有着较大的简化作用。

流的概念可能最早来自于列表(List),列表可以理解为按顺序排列的一组对象(数组和链表都是其具体实现)。

大多数程序的最外在特征是给定一个输入后,按照某种规则得出相应的输出。编写由输入到输出的规则就是programmer所做的事情了。许多程序的规则都可以被抽象为三部分:

  • 根据输入产生一组数据
  • 对第一步产生的数据组进行处理
  • 对处理过后的数据约简而得到最终的输出

当然,最后约简的操作也可以算作数据处理的一部分。但由于它是最后一步操作,所以往往将它独立出来。

这种抽象可以类比为国家选拔人才的机制。

  • 随着形式的变化,国家向大学提出了向H部门输送X专业的高级人才的要求。(这相当于用户输入)
  • 大学招收了一群X专业的本科新生。(产生了一组数据)
  • 大学对这些新生进行专业教育,淘汰掉挂科的学生。(处理数据)
  • 毕业之时,将成绩优异的学生推荐给H部门。(约简得到输出)

也许正因为这种对过程的抽象方式天然地存在于人的大脑结构之中(是某种先验的思维模式),我们才会很自然地将无论是社会还是计算机中的许多过程都按照这种方式进行抽象。

从List到Stream

程序的三部分抽象中有两个关键部分:一是如何表示数据,二是如何处理数据。

对于数据的表示,我们很自然地会想到使用List这样的计算机能支持的最简单数据集合来表示。

对于数据的处理方式,我们抽象出了许多种类,比如说:

map: 对于List中的每一项数据都进行某种操作

filter: 删除List中某些不需要的元素

count: 得到List中总的元素数目

有些处理方式(比如说count),对List操作之后得到的并不是List,不再能连续地进行下一步操作,所以只能作为最后一步约简地处理方式。

然而用一般计算机语言中的List表示数据组,却有以下两种缺点:

  • 一,不能表示无限数据组
  • 二,每次处理都必须对每个元素都进行处理,造成了资源的浪费。(但实际上我们的程序可能只需要处理前几个数据就可以得出结果了)

因此,出现了一种新的数据抽象,流(stream)。流的主要特征即是惰性求值。而惰性求值很好地避免了以上两个问题。所谓惰性求值,即需要的时候再进行求值。

比方说我们的数据组是一串5个白色乒乓球。要对这些乒乓球进行如下处理,首先是染蓝色颜料,其次染黄色颜料,最后我们要拿到第二个染色后的乒乓球。按照List的处理逻辑,我们要先把所有的球染成蓝色,然后将所有的球染成黄色,最后再取出第二个球。但是按照stream的处理逻辑,我们首先知道了要把球 染成蓝色,但我们先记住这个命令,却不实际操作。然后记住要染黄色的命令,也不实际操作。在最后一步,我们要拿出第二个染色后的球。这时候我们再依次对这些球进行处理。先处理完第一个球,然后处理第二个球,这时直接拿出第二个球即可, 而不需要对剩余球进行染色。

此处笔者自感表达不清,关于stream的解释详见SICP3.5

stream API

由于stream的强大抽象能力,java8中新引入了stream API。java8中的stream即是上述概念模型的一种实现,并无特殊性。其主要操作自然也是分为stream的构造,处理以及约简三部分。下面三部分将分别记录常用的API。

构造

由collection或Array转化

Collection:

default Stream<E> stream()

Array:

public static <T> Stream<T> stream(T[] array)

public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)

public static IntStream stream(int[] array)

public static IntStream stream(int[] array, int startInclusive, int endExclusive)

以及类似的DoubleStream和LongStream方法

由Stream直接创建

Stream:

static <T> Stream<T> empty()

Returns an empty sequential Stream.

static <T> Stream<T> of(T t)

Returns a sequential Stream containing a single element.

static <T> Stream<T> ofNullable(T t)

Returns a sequential Stream containing a single element, if non-null, otherwise returns an empty Stream.

@SafeVarargs static <T> Stream<T> of(T... values)

Returns a sequential ordered stream whose elements are the specified values.

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)

Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.

static <T> Stream<T> generate(Supplier<? extends T> s)

Returns an infinite sequential unordered stream where each element is generated by the provided Supplier. This is suitable for generating constant streams, streams of random elements, etc.

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

Creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream.

另外也可以通过streamBuilder类创建stream

处理

Stream:

Stream<T> filter(Predicate<? super T> predicate)

Returns a stream consisting of the elements of this stream that match the given predicate. This is an intermediate operation.

<R> Stream<R> map(Function<? super T,​? extends R> mapper)

Returns a stream consisting of the results of applying the given function to the elements of this stream. This is an intermediate operation.

Stream<T> limit(long maxSize)

Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length. This is a short-circuiting stateful intermediate operation.

Stream<T> skip(long n)

Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned. This is a stateful intermediate operation.

Stream<T> sorted()

Returns a stream consisting of the elements of this stream, sorted according to natural order. If the elements of this stream are not Comparable, a java.lang.ClassCastException may be thrown when the terminal operation is executed. For ordered streams, the sort is stable. For unordered streams, no stability guarantees are made.

This is a stateful intermediate operation.

Stream<T> sorted(Comparator<? super T> comparator) Returns a stream consisting of the elements of this stream, sorted according to the provided Comparator. For ordered streams, the sort is stable. For unordered streams, no stability guarantees are made.

This is a stateful intermediate operation.

约简

Stream:

void forEach(Consumer<? super T> action)

Performs an action for each element of this stream. This is a terminal operation.

Optional<T> findFirst()

Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty. If the stream has no encounter order, then any element may be returned. This is a short-circuiting terminal operation.

Optional<T> max(Comparator<? super T> comparator)

Returns the maximum element of this stream according to the provided Comparator. This is a special case of a reduction. This is a terminal operation.

Optional<T> min(Comparator<? super T> comparator)

Returns the minimum element of this stream according to the provided Comparator. This is a special case of a reduction. This is a terminal operation.

T reduce(T identity, BinaryOperator<T> accumulator)

Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value. This is equivalent to:

T result = identity;

for (T element : this stream)

	result = accumulator.apply(result, element)

return result;

but is not constrained to execute sequentially. The identity value must be an identity for the accumulator function. This means that for all t, accumulator.apply(identity, t) is equal to t. The accumulator function must be an associative function.

This is a terminal operation.

Optional<T> reduce(BinaryOperator<T> accumulator)

Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any. This is equivalent to:

 boolean foundAny = false;
     T result = null;
     for (T element : this stream) {
         if (!foundAny) {
             foundAny = true;
             result = element;
         }
         else
             result = accumulator.apply(result, element);
     }
     return foundAny ? Optional.of(result) : Optional.empty();

but is not constrained to execute sequentially. The accumulator function must be an associative function.This is a terminal operation.