流式编程一

本文详细介绍Java8中流(Stream)的特性和创建方法,包括Stream接口、Collection接口、Random类、IntStream、Arrays类及正则表达式提供的流创建方式。

一、流特性概述

  1. 使用流,无需迭代集合中的元素,就可以从管道提取和操作元素。这些管道通常被组合在一起,形成一系列对流进行操作的管道。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用
  2. 声明式编程(Declarative programming)是一种编程风格,它声明想要做什么,而非指明如何做(命令式编程)
  3. 显式地编写迭代过程的方式称为外部迭代,而内部迭代是流式编程的一个核心特征,无须显示的书写繁琐的迭代过程。
  4. 流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。
  5. 流在执行最终操作后无法被复用

二、流支持

如何将一个全新的流的概念融入到现有类库中,且不影响现有代码?最大的难题来自于使用接口的库。集合类是其中关键的一部分,因为你想把集合转为流。但是如果你将一个新方法添加到接口,那就破坏了每一个实现接口的类,因为这些类都没有实现你添加的新方法。

Java 8 采用的解决方案是:在接口中添加被 default默认)修饰的方法。通过这种方案,设计者们可以将流式(stream)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。

三、流创建

创建空流,空流是通过 Stream.empty() 创建的。如果你在没有任何上下文环境的情况下调用 Stream.empty(),Java 并不知道它的数据类型;Stream.<String>empty()这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:Stream<String> s = Stream.empty()就可以在调用 empty() 时推断类型。

1. Stream接口自身提供的流创建方法
  1. 你可以通过 Stream.of() 很容易地将一组元素转化成为流,该流是连续的且有序(符合给定元素的顺序)的
    /**
     * Returns a sequential {@code Stream} containing a single element.
     *
     * @param t the single element
     * @param <T> the type of stream elements
     * @return a singleton sequential stream
     */
    public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }

    /**
     * Returns a sequential ordered stream whose elements are the specified values.
     *
     * @param <T> the type of stream elements
     * @param values the elements of the new stream
     * @return the new stream
     */
    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
  1. 使用generate方法,可生成一个无限连续但无序的流,流中所有元素都符合由参数supplier指定的规则
    /**
     * Returns an infinite sequential unordered stream where each element is
     * generated by the provided {@code Supplier}.  This is suitable for
     * generating constant streams, streams of random elements, etc.
     *
     * @param <T> the type of stream elements
     * @param s the {@code Supplier} of generated elements
     * @return a new infinite sequential unordered {@code Stream}
     */
    public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }
  1. 使用iterate方法生成
  /**
     * Returns an infinite sequential ordered {@code Stream} produced by iterative
     * application of a function {@code f} to an initial element {@code seed},
     * producing a {@code Stream} consisting of {@code seed}, {@code f(seed)},
     * {@code f(f(seed))}, etc.
     *
     * <p>The first element (position {@code 0}) in the {@code Stream} will be
     * the provided {@code seed}.  For {@code n > 0}, the element at position
     * {@code n}, will be the result of applying the function {@code f} to the
     * element at position {@code n - 1}.
     * 
     * 后一个元素是由前一个元素在执行UnaryOperator接口的apply函数后产生的结果
     *
     * @param <T> the type of stream elements
     * @param seed the initial element
     * @param f a function to be applied to to the previous element to produce
     *          a new element
     * @return a new sequential {@code Stream}
     */
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        Objects.requireNonNull(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() {
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iterator,
                Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
    }

// 下面是一个斐波那契数列生成代码
import java.util.stream.*;
public class Fibonacci {
    int x = 1;
    
    Stream<Integer> numbers() {
        return Stream.iterate(0, i -> {
            int result = x + i;
            x = i;  // iterate()只能记忆结果,因此我们需要利用一个变量x追踪另外一个元素
            return result;
        });
    }
    
    public static void main(String[] args) {
        new Fibonacci().numbers()
                       .limit(10) // 然后取 10 个
                       .forEach(System.out::println);
    }
}
  1. 建造者模式,使用构造器,在建造者模式(Builder design pattern)中,首先创建一个 builder 对象,然后将创建流所需的多个信息传递给它,最后builder 对象执行”创建“流的操作。Stream 库提供了这样的 Builder。通过这样的方式,可量产同性质的流对象。
    public static<T> Builder<T> builder() {
        return new Streams.StreamBuilderImpl<>();
    }

 	 public interface Builder<T> extends Consumer<T> {

        /**
         * Adds an element to the stream being built.
         *
         * @throws IllegalStateException if the builder has already transitioned to
         * the built state
         */
        @Override
        void accept(T t);

        default Builder<T> add(T t) {
            accept(t);
            return this;
        }

        /**
         * Builds the stream, transitioning this builder to the built state.
         * An {@code IllegalStateException} is thrown if there are further attempts
         * to operate on the builder after it has entered the built state.
		 *
		 * 在执行build方法后,此builder将会被标记为built状态,之后任何尝试对此builder进行
		 * 修改的操作都会抛出 IllegalStateException 异常
         *
         * @return the built stream
         * @throws IllegalStateException if the builder has already transitioned to
         * the built state
         */
        Stream<T> build();

    }
2. Collection接口提供的流创建方法

除此之外,集合接口Collection中新定义了default方法stream以及parallelStream,每个集合都可以通过调用 stream() 方法来产生一个流

 /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a sequential {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a sequential {@code Stream} over the elements in this collection
     * @since 1.8
     */
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    /**
     * Returns a possibly parallel {@code Stream} with this collection as its
     * source.  It is allowable for this method to return a sequential stream.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a parallel {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a possibly parallel {@code Stream} over the elements in this
     * collection
     * @since 1.8
     */
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }

这里顺便介绍一下Collection新增的另外两个default方法

	// 移除集合中所有符合过滤条件的元素
    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }
    
	// 并行迭代器,参考https://www.cnblogs.com/nevermorewang/p/9368431.html
	@Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
3. Random类提供的流创建方法

仔细观察,你可以发现,上述生成的流都是泛型流对象,而基本数据类型是不支持泛型的。所以java 设计者必须为其早期的草率决定付出代价,在函数式接口以及流接口的定义上,就需要预定义一些额外的接口。如IntStream、DoubleStream、LongStream等等

jdk1.8之后,java.util.Random中新增了生成 随机数流的对应方法ints()、longs()、doubles(),这里仅以其中一个举例

	// 生成包含streamSize个元素的流,所有元素符合范围[randomNumberOrigin,randomNumberBound)
    public IntStream ints(long streamSize, int randomNumberOrigin,
                          int randomNumberBound) {
        if (streamSize < 0L)
            throw new IllegalArgumentException(BadSize);
        if (randomNumberOrigin >= randomNumberBound)
            throw new IllegalArgumentException(BadRange);
        return StreamSupport.intStream
                (new RandomIntsSpliterator
                         (this, 0L, streamSize, randomNumberOrigin, randomNumberBound),
                 false);
    }

如果想要将IntStream 转化为Stream,可调用boxed()方法。 boxed() 流操作将会自动地把基本类型包装成为对应的装箱类型

4. IntStream流boxed()

IntStream中定义了将自身转换为对应包装类型Stream流的抽象方法,其他如DoubleStream,LongStream同理。

   /**
     * Returns a {@code Stream} consisting of the elements of this stream,
     * each boxed to an {@code Integer}.
     *
     * @return a {@code Stream} consistent of the elements of this stream,
     * each boxed to an {@code Integer}
     */
    Stream<Integer> boxed();

这里顺带再简单介绍一下IntStream,其自身也提供了一些生成IntStream流对象的方法,如of方法,而range方法可生成一个步长为1的包含递增元素的IntStream流(rangeClosed方法包含了上限值,即左右闭区间)

/**
     * Returns a sequential ordered {@code IntStream} from {@code startInclusive}
     * (inclusive) to {@code endExclusive} (exclusive) by an incremental step of
     * {@code 1}.
     *
     * @apiNote
     * <p>An equivalent sequence of increasing values can be produced
     * sequentially using a {@code for} loop as follows:
     * <pre>{@code
     *     for (int i = startInclusive; i < endExclusive ; i++) { ... }
     * }</pre>
     *
     * @param startInclusive the (inclusive) initial value
     * @param endExclusive the exclusive upper bound
     * @return a sequential {@code IntStream} for the range of {@code int}
     *         elements
     */
    public static IntStream range(int startInclusive, int endExclusive) {
        if (startInclusive >= endExclusive) {
            return empty();
        } else {
            return StreamSupport.intStream(
                    new Streams.RangeIntSpliterator(startInclusive, endExclusive, false), false);
        }
    }
 // range方法默认步长为1,且不提供其他步长选项的重载方法,但是实现起来也很简单,demo:以3为步长,生成包含[2,20)之间所有元素的流
  IntStream.range(2, 20).filter(i -> (i - 2) % 3 == 0).forEach(System.out::println);
5. Arrays类stream()方法

Arrays 类中含有一个名为 stream() 的静态方法用于把数组转换成为流。

 /**
     * Returns a sequential {@link Stream} with the specified array as its
     * source.
     *
     * @param <T> The type of the array elements
     * @param array The array, assumed to be unmodified during use
     * @return a {@code Stream} for the array
     * @since 1.8
     */
    public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }

    /**
     * Returns a sequential {@link Stream} with the specified range of the
     * specified array as its source.
     *
     * @param <T> the type of the array elements
     * @param array the array, assumed to be unmodified during use
     * @param startInclusive the first index to cover, inclusive
     * @param endExclusive index immediately past the last index to cover
     * @return a {@code Stream} for the array range
     * @throws ArrayIndexOutOfBoundsException if {@code startInclusive} is
     *         negative, {@code endExclusive} is less than
     *         {@code startInclusive}, or {@code endExclusive} is greater than
     *         the array size
     * @since 1.8
     */
    public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
        return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
    }

除了可以转换为Stream以外,还可以转换为对应的IntStream,DoubleStream等,这里就不一一介绍了

6. 正则表达式

Java 8 在 java.util.regex.Pattern 中增加了一个新的方法 splitAsStream()。这个方法可以根据传入的公式将字符序列转化为流。如:

Pattern.compile("[ .,?]+").splitAsStream(all)

但是有一个限制,输入只能是 CharSequence,因此不能将流作为 splitAsStream() 的参数。因为用的很少,这里不做过多介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值