文章目录
一、流特性概述
- 使用流,无需迭代集合中的元素,就可以从管道提取和操作元素。这些管道通常被组合在一起,形成一系列对流进行操作的管道。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用
- 声明式编程(Declarative programming)是一种编程风格,它声明想要做什么,而非指明如何做(命令式编程)
- 显式地编写迭代过程的方式称为外部迭代,而内部迭代是流式编程的一个核心特征,无须显示的书写繁琐的迭代过程。
- 流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。
- 流在执行最终操作后无法被复用
二、流支持
如何将一个全新的流的概念融入到现有类库中,且不影响现有代码?最大的难题来自于使用接口的库。集合类是其中关键的一部分,因为你想把集合转为流。但是如果你将一个新方法添加到接口,那就破坏了每一个实现接口的类,因为这些类都没有实现你添加的新方法。
Java 8 采用的解决方案是:在接口中添加被 default(默认)修饰的方法。通过这种方案,设计者们可以将流式(stream)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。
三、流创建
创建空流,空流是通过 Stream.empty() 创建的。如果你在没有任何上下文环境的情况下调用 Stream.empty(),Java 并不知道它的数据类型;Stream.<String>empty()这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:Stream<String> s = Stream.empty()就可以在调用 empty() 时推断类型。
1. Stream接口自身提供的流创建方法
- 你可以通过
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);
}
- 使用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);
}
- 使用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);
}
}
- 建造者模式,使用构造器,在建造者模式(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() 的参数。因为用的很少,这里不做过多介绍
本文详细介绍Java8中流(Stream)的特性和创建方法,包括Stream接口、Collection接口、Random类、IntStream、Arrays类及正则表达式提供的流创建方式。
332

被折叠的 条评论
为什么被折叠?



