Java8 Stream的簡易實現(xiàn)

前言

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的讀者一定用過mapfilter這兩個常用的流操作州藕,下面我們就來實現(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

后記

完整代碼:https://github.com/byx2000/simple-stream

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市妙真,隨后出現(xiàn)的幾起案子缴允,更是在濱河造成了極大的恐慌,老刑警劉巖珍德,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件练般,死亡現(xiàn)場離奇詭異菱阵,居然都是意外死亡踢俄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門晴及,熙熙樓的掌柜王于貴愁眉苦臉地迎上來都办,“玉大人,你說我怎么就攤上這事虑稼×斩ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵蛛倦,是天一觀的道長歌懒。 經(jīng)常有香客問我,道長溯壶,這世上最難降的妖魔是什么及皂? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮且改,結果婚禮上验烧,老公的妹妹穿的比我還像新娘。我一直安慰自己又跛,他們只是感情好碍拆,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著慨蓝,像睡著了一般感混。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上礼烈,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天弧满,我揣著相機與錄音,去河邊找鬼此熬。 笑死谱秽,一個胖子當著我的面吹牛洽蛀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疟赊,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼峡碉!你這毒婦竟也來了近哟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鲫寄,失蹤者是張志新(化名)和其女友劉穎吉执,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體地来,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡戳玫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了未斑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咕宿。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜡秽,靈堂內(nèi)的尸體忽然破棺而出府阀,到底是詐尸還是另有隱情,我是刑警寧澤芽突,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布试浙,位于F島的核電站,受9級特大地震影響寞蚌,放射性物質(zhì)發(fā)生泄漏田巴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一挟秤、第九天 我趴在偏房一處隱蔽的房頂上張望壹哺。 院中可真熱鬧,春花似錦煞聪、人聲如沸斗躏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啄糙。三九已至,卻和暖如春云稚,著一層夾襖步出監(jiān)牢的瞬間隧饼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工静陈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留燕雁,地道東北人诞丽。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像拐格,于是被迫代替她去往敵國和親僧免。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容

  • stream描述 用于支持元素流上的功能樣式操作的類捏浊,例如集合上的map-reduce轉換懂衩。例如: 這里我們使用w...
    yyg閱讀 340評論 0 0
  • 流是Java API的新成員,它允許你以聲明性方式處理數(shù)據(jù)集合金踪,還可以透明地并行處理浊洞;你可以把它們看成遍歷數(shù)據(jù)集的...
    夏與清風閱讀 872評論 0 0
  • 篩選和切片 filter 方法 distinct 方法 limit 方法 skip 方法 謂詞篩選 Stream ...
    劉滌生閱讀 2,998評論 2 4
  • Java 8 中的 Stream 是對集合(Collection)對象功能的增強,它專注于對集合對象進行各種非常便...
    Gojo99閱讀 457評論 0 0
  • 使用流操作來表達復雜的數(shù)據(jù)處理查詢胡岔。 集合是Java中使用多的API法希。要是沒有集合,還能做什么呢靶瘸?幾乎每個Java...
    bern85閱讀 866評論 0 3