前言
Java8新增的Stream API是一個強大的特性攻礼,它可以簡化集合中的常用操作业踢,包括過濾、映射礁扮、分組等知举。下面就來實現(xiàn)一個簡易版的Stream。
從表面上看太伊,流似乎和列表很接近雇锡,但實際上它們有著本質(zhì)的區(qū)別。
列表是多個元素的容器僚焦,當列表被創(chuàng)建出來時锰提,它里面的每個元素也已經(jīng)被創(chuàng)建出來了。
流是一種計算結構芳悲,它封裝了內(nèi)部元素如何產(chǎn)生的計算過程立肘,但是并沒有包含實際的元素數(shù)據(jù)。換句話說名扛,當一個流被創(chuàng)建出來時谅年,它內(nèi)部的元素并沒有被創(chuàng)建,但是我們可以通過調(diào)用流的方法來按順序生成每個元素肮韧。
所以融蹂,流具有惰性計算的特性,它可以表示普通列表無法表示的一些結構弄企,如無限流超燃。
流的定義
流的定義看起來很像鏈表,一個流由兩部分組成:第一個元素(first)和剩余元素組成的流(remain)拘领。定義如下:
public interface Stream<T> {
/**
* 流中第一個元素
*/
T first();
/**
* 剩余元素組成的流
*/
Stream<T> remain();
/**
* 創(chuàng)建流
* @param firstSupplier 第一個元素的工廠
* @param remainSupplier 剩余元素組成的流的工廠
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> create(Supplier<T> firstSupplier, Supplier<Stream<T>> remainSupplier) {
return new Stream<>() {
@Override
public T first() {
return firstSupplier.get();
}
@Override
public Stream<T> remain() {
return remainSupplier.get();
}
};
}
}
這種遞歸的定義非常有利于使用遞歸算法來操作流意乓。下面可以看到,流的大多數(shù)相關操作都是用遞歸算法實現(xiàn)的约素。
假設我們已經(jīng)有了一個流洽瞬,那么如何獲取流中的元素呢?首先調(diào)用first
來獲取第一個元素业汰,然后調(diào)用remain().first()
來獲取第二個元素,依此類推:
Stream<Integer> stream = ...
Integer first = stream.first(); // 第一個元素
Integer second = stream.remain().first(); // 第二個元素
Integer third = stream.remain().remain().first(); // 第三個元素
// 依此類推...
當然菩颖,我們不會用這種方法來訪問流中的元素样漆。具體如何訪問,請繼續(xù)往下看晦闰。
空流
空流是最簡單的流放祟,無法從空流中獲取任何元素鳍怨。空流也標志著一個流的結束跪妥。下面是空流的實現(xiàn):
Stream<?> EMPTY = create(
() -> {throw new IllegalStateException("當前流已結束");},
() -> {throw new IllegalStateException("當前流已結束");}
);
/**
* 獲取空流
*/
@SuppressWarnings("unchecked")
static <T> Stream<T> empty() {
return (Stream<T>) EMPTY;
}
/**
* 判斷當前流是否結束
*/
default boolean end() {
return this == EMPTY;
}
有限流的生成
有限流可以通過多種方式生成鞋喇,包括從數(shù)組生成、從迭代器生成眉撵、從集合生成侦香。
從數(shù)組生成流
/**
* 從數(shù)組生成流
* @param arr 數(shù)組
* @param <T> 元素類型
* @return 流
*/
@SafeVarargs
static <T> Stream<T> of(T... arr) {
return fromArray(0, arr);
}
/**
* 從數(shù)組和起始索引生成流
* @param startIndex 起始索引
* @param arr 數(shù)組
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> fromArray(int startIndex, T[] arr) {
return startIndex == arr.length
? empty()
: create(() -> arr[startIndex], () -> fromArray(startIndex + 1, arr));
}
從迭代器生成流
/**
* 從迭代器生成流
* @param iterator 迭代器
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> fromIterator(Iterator<T> iterator) {
return iterator.hasNext()
? create(iterator::next, () -> fromIterator(iterator))
: empty();
}
從集合生成流
/**
* 從集合生成流
* @param collection 集合
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> fromCollection(Collection<T> collection) {
return fromIterator(collection.iterator());
}
示例
Stream<Integer> s1 = Stream.of(1, 2, 3); // 從數(shù)組生成
Stream<Integer> s2 = Stream.fromIterator(List.of(1, 2, 3).iterator()); // 從迭代器生成
Stream<Integer> s3 = Stream.fromCollection(Set.of(1, 2, 3)); // 從集合生成
無限流的生成
無限流意味著流中的元素個數(shù)沒有限制,也就是永遠都不會結束纽疟,所以end
方法調(diào)用永遠為false
罐韩。有以下兩種方法生成無限流。
從工廠方法生成流
/**
* 從工廠方法生成流
* @param supplier 生成流中元素的工廠方法
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> fromSupplier(Supplier<T> supplier) {
return create(supplier, () -> fromSupplier(supplier));
}
從生成器生成流
/**
* 迭代生成流
* @param initial 初始值
* @param generator 生成器
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> fromGenerator(T initial, UnaryOperator<T> generator) {
return create(() -> initial, () -> generate(generator.apply(initial), generator));
}
示例
Stream<Integer> s1 = Stream.fromSupplier(() -> 1); // 無限個1組成的流
Stream<Integer> s2 = Stream.fromGenerator(1, n -> n + 1); // 全體自然數(shù)組成的流
遍歷流中的元素
知道了如何創(chuàng)建流污朽,那么如何遍歷或輸出流中的元素呢散吵?可以實現(xiàn)下面的forEach
方法:
/**
* 遍歷流中所有元素
* @param consumer 遍歷操作
*/
default void forEach(Consumer<T> consumer) {
Stream<T> s = this;
while (!s.end()) {
consumer.accept(s.first());
s = s.remain();
}
}
然后就可以像下面這樣輸出流中的元素:
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
s.forEach(System.out::println); // 輸出1 2 3 4 5
流的截斷和偏移
上面的forEach
方法只適用于有限流,如果在無限流上調(diào)用forEach
方法蟆肆,會導致死循環(huán)矾睦。所以,我們需要對無限流進行截取操作炎功,這樣就能做到遍歷無限流的一部分枚冗。
/**
* 截取流中前n個元素
* @param n 要截取的元素個數(shù)
* @return 流
*/
default Stream<T> limit(int n) {
return n <= 0 || end()
? empty()
: create(this::first, () -> remain().limit(n - 1));
}
/**
* 跳過流中的元素
* @param n 跳過的個數(shù)
* @return 流
*/
default Stream<T> skip(int n) {
return end() || n <= 0
? this
: remain().skip(n - 1);
}
limit
用于提取流的前n個元素,skip
用于忽略流的前n個元素亡问,有了這兩個方法官紫,我們就能隨心所欲地截取任何流中的任意一段。
流的變換操作
熟悉Java8 Stream API的讀者一定用過map
和filter
這兩個常用的流操作州藕,下面我們就來實現(xiàn)它們束世。
map
map
用于對流中的所有元素進行轉換操作。
/**
* 映射流中的元素
* @param mapper 映射器
* @param <U> 映射后的元素類型
* @return 流
*/
default <U> Stream<U> map(Function<T, U> mapper) {
return end()
? empty()
: create(() -> mapper.apply(first()), () -> remain().map(mapper));
}
filter
filter
用于過濾流中的元素床玻。
/**
* 過濾流中的元素
* @param predicate 斷言
* @return 流
*/
default Stream<T> filter(Predicate<T> predicate) {
if (end()) {
return empty();
}
T e = first();
if (predicate.test(e)) {
return Stream.create(() -> e, () -> remain().filter(predicate));
} else {
return remain().filter(predicate);
}
}
示例
Stream<String> s = Stream.of(1, 2, 3, 4, 5, 6)
.filter(n -> n % 2 == 0) // 2, 4, 6
.map(n -> "hello " + n); // hello 2, hello 4, hello 6
流的聚合操作
有時候我們像將整個流聚合成某種數(shù)據(jù)結構毁涉,如列表、集合等锈死,這就需要用到流的聚合操作贫堰。
collect
對流進行自定義聚合操作。
/**
* 流的聚合操作
* @param initial 初始值
* @param accumulator 聚合操作
* @param <U> 聚合后的類型
* @return 流
*/
default <U> U collect(U initial, BiFunction<U, T, U> accumulator) {
U result = initial;
Stream<T> s = this;
while (!s.end()) {
result = accumulator.apply(result, s.first());
s = s.remain();
}
return result;
}
toList
將流轉換成列表待牵。
/**
* 將流轉換成列表
* @return 列表
*/
default List<T> toList() {
return collect(new ArrayList<>(), (list, e) -> {
list.add(e);
return list;
});
}
toSet
將流轉換成集合其屏。
/**
* 將流轉換成集合
* @return 集合
*/
default Set<T> toSet() {
return collect(new HashSet<>(), (set, e) -> {
set.add(e);
return set;
});
}
toMap
將流轉換成Map
。
/**
* 將流轉換成map
* @param keyGenerator key生成器
* @param valueGenerator value生成器
* @param <K> key的類型
* @param <V> value的類型
* @return map
*/
default <K, V> Map<K, V> toMap(Function<T, K> keyGenerator, Function<T, V> valueGenerator) {
return collect(new HashMap<>(), (map, e) -> {
map.put(keyGenerator.apply(e), valueGenerator.apply(e));
return map;
});
}
count
對流中的元素進行計數(shù)缨该。
/**
* 獲取流中元素個數(shù)
* @return 元素個數(shù)
*/
default int count() {
return collect(0, (cnt, e) -> cnt + 1);
}
流的高級操作
下面是流的一些高級操作偎行。
concat
concat
用于將兩個流首尾連接在一起。
/**
* 首尾連接兩個流
* s1[0] -> s1[1] -> s2[2] -> ... -> s2[0] -> s2[1] -> s2[2] -> ...
* @param s1 s1
* @param s2 s2
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> concat(Stream<T> s1, Stream<T> s2) {
return s1.end()
? s2
: create(s1::first, () -> concat(s1.remain(), s2));
}
/**
* 首尾連接兩個流
* @param s 要連接的流
* @return 流
*/
default Stream<T> concat(Stream<T> s) {
return concat(this, s);
}
示例:
Stream<Integer> s1 = Stream.of(1, 2, 3, 4);
Stream<Integer> s2 = Stream.of(5, 6, 7);
Stream<Integer> s = s1.concat(s2); // 1, 2, 3, 4, 5, 6, 7
interleave
interleave
用于將兩個流交錯連接在一起。
/**
* 交錯連接兩個流
* s1[0] -> s2[0] -> s1[1] -> s2[1] -> ...
* @param s1 s1
* @param s2 s2
* @param <T> 元素類型
* @return 流
*/
static <T> Stream<T> interleave(Stream<T> s1, Stream<T> s2) {
return s1.end()
? s2
: create(s1::first, () -> interleave(s2, s1.remain()));
}
/**
* 交錯連接兩個流
* @param s 要連接的流
* @return 流
*/
default Stream<T> interleave(Stream<T> s) {
return interleave(this, s);
}
示例:
Stream<Integer> s1 = Stream.of(1, 3, 5, 7);
Stream<Integer> s2 = Stream.of(2, 4, 6);
Stream<Integer> s = s1.interleave(s2); // 1, 2, 3, 4, 5, 6, 7
flatMap
flatMap
用于將流中的每個元素都映射成一個流蛤袒,然后將所有流連接起來熄云。
/**
* 扁平化流
* @param mapper 元素到流的映射器
* @param <U> 扁平化后的元素類型
* @return 流
*/
default <U> Stream<U> flatMap(Function<T, Stream<U>> mapper) {
return collect(empty(), (s, e) -> s.concat(mapper.apply(e)));
}
示例:
Stream<Integer> s = Stream.of(10, 20)
.flatMap(n -> Stream.of(n + 1, n + 2, n + 3)); // 11, 12, 13, 21, 22, 23