Java中的Stream流式操作大全
一、介紹
在 Java 8 及以上版本中,Stream(流式操作) 是處理集合(Collection)、數(shù)組等數(shù)據(jù)的高效工具,它基于函數(shù)式編程思想,提供了聲明式(而非命令式)的數(shù)據(jù)處理方式,核心優(yōu)勢是 簡潔、高效、可并行。
Stream 本質(zhì)是數(shù)據(jù)的 “流水線”:數(shù)據(jù)從數(shù)據(jù)源(集合、數(shù)組等)流入,經(jīng)過一系列中間操作(過濾、映射、排序等)的 “加工”,最終通過終端操作(統(tǒng)計(jì)、收集、遍歷等)輸出結(jié)果(或無輸出)。
這個 “流水線” 可以用 “工廠食品加工生產(chǎn)線” 來形象比喻,每個環(huán)節(jié)和 Stream 操作一一對應(yīng),特別好理解:
你可以把 Stream 想象成一條 自動化食品加工生產(chǎn)線:
- 「數(shù)據(jù)源」= 生產(chǎn)線的 “原料倉庫”:比如一堆新鮮的蔬菜(對應(yīng)集合 / 數(shù)組里的原始數(shù)據(jù)),是加工的起點(diǎn);
- 「中間操作」= 生產(chǎn)線的 “加工工序”:比如 “篩選(去掉腐爛的蔬菜)”“切割(切成小塊)”“清洗(去掉泥沙)”“調(diào)味(加調(diào)料)”,每個工序只負(fù)責(zé)自己的加工,不直接產(chǎn)出最終產(chǎn)品(惰性求值),而是把加工后的原料傳到下一道工序;
- 「終端操作」= 生產(chǎn)線的 “成品包裝 / 出庫”:比如 “裝盒打包”“稱重計(jì)數(shù)”“直接運(yùn)往超市”,只有啟動這一步,前面所有加工工序才會真正運(yùn)轉(zhuǎn)(觸發(fā)計(jì)算),最終產(chǎn)出成品(對應(yīng) Stream 的結(jié)果)或完成交付(比如 forEach 遍歷相當(dāng)于 “把食品分發(fā)給消費(fèi)者”,無返回值)。
補(bǔ)充兩個貼合 Stream 特性的細(xì)節(jié):
- 原料只能用一次:同一批蔬菜(同一個 Stream)經(jīng)過生產(chǎn)線(終端操作)后,就變成了成品,不能再放回倉庫重新加工(Stream 一次性消費(fèi));
可選 “并行加工”:普通生產(chǎn)線是單條流水線(串行流),如果原料太多,可啟動多條并行流水線(并行流),同時處理不同批次的原料,最后合并成品,效率更高。
二、特性
- 惰性求值:中間操作不會立即執(zhí)行,只有調(diào)用終端操作時,才會觸發(fā)整個流水線的計(jì)算(節(jié)省資源)。
- 一次性消費(fèi):一個 Stream 只能被使用一次,執(zhí)行終端操作后會自動關(guān)閉,再次使用會拋出異常。
- 無狀態(tài) / 有狀態(tài)操作:
- 無狀態(tài):每個元素的處理獨(dú)立(如
filter(篩選)、map(映射)),不依賴其他元素; - 有狀態(tài):處理元素需要依賴其他元素(如
sorted(自然排序)、distinct(去重)、limit(限制)),可能需要緩存數(shù)據(jù)。
- 無狀態(tài):每個元素的處理獨(dú)立(如
- 并行支持:通過
parallelStream()或stream.parallel()輕松實(shí)現(xiàn)并行處理(底層依賴 Fork/Join 框架)。
三、操作
完整的 Stream 操作分為 3 步:
- 創(chuàng)建 Stream:從數(shù)據(jù)源(集合、數(shù)組、生成器等)獲取 Stream;
- 中間操作:對數(shù)據(jù)進(jìn)行過濾、映射、排序等加工(可多個中間操作鏈?zhǔn)秸{(diào)用);
- 終端操作:觸發(fā)計(jì)算,輸出結(jié)果(或執(zhí)行副作用,如遍歷),Stream 生命周期結(jié)束。
流程圖:
集合/數(shù)組 → 創(chuàng)建 Stream → 中間操作1 → 中間操作2 → ... → 終端操作 → 結(jié)果
四、詳解
1. 創(chuàng)建 Stream(數(shù)據(jù)源)
(1)從集合創(chuàng)建(最常用)
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 串行流
Stream<String> parallelStream = list.parallelStream(); // 并行流(2)從數(shù)組創(chuàng)建
String[] arr = {"x", "y", "z"};
Stream<String> stream = Arrays.stream(arr);
// 部分?jǐn)?shù)組(從索引1到2,左閉右開)
Stream<String> partStream = Arrays.stream(arr, 1, 3);(3)通過 Stream.of () 創(chuàng)建
Stream<Integer> stream = Stream.of(1, 2, 3, 4); Stream<Object> emptyStream = Stream.empty(); // 空流
(4)無限流(生成器 / 迭代器)
適用于需要動態(tài)生成數(shù)據(jù)的場景(需配合 limit 限制長度,否則無限循環(huán)):
// 1. generate:生成固定值或隨機(jī)值(Supplier接口) Stream<Double> randomStream = Stream.generate(Math::random).limit(5); // 5個隨機(jī)數(shù) // 2. iterate:迭代生成(種子 -> 下一個元素,UnaryOperator接口) Stream<Integer> numStream = Stream.iterate(0, n -> n + 2).limit(5); // 0,2,4,6,8
2. 中間操作(加工數(shù)據(jù))
中間操作返回新的 Stream,支持鏈?zhǔn)秸{(diào)用,常用操作如下:
| 操作 | 功能描述 | 示例(以 List<Integer> list = [1,2,3,4,5,6] 為例) |
|---|---|---|
filter(Predicate) | 過濾元素(滿足條件保留) | list.stream().filter(n -> n % 2 == 0) → [2,4,6] |
map(Function) | 元素映射(轉(zhuǎn)換為另一種類型) | list.stream().map(n -> n * 2) → [2,4,6,8,10,12] |
flatMap(Function) | 扁平化映射(將流中的流展開為單個流) | List<List<Integer>> list2 = [[1,2],[3,4]]; list2.stream().flatMap(Collection::stream) → [1,2,3,4] |
distinct() | 去重(基于 equals() 方法) | Stream.of(1,2,2,3).distinct() → [1,2,3] |
sorted() | 自然排序(元素需實(shí)現(xiàn) Comparable) | Stream.of(3,1,2).sorted() → [1,2,3] |
sorted(Comparator) | 自定義排序 | list.stream().sorted((a,b) -> b - a) → [6,5,4,3,2,1] |
limit(long) | 限制返回前 N 個元素 | list.stream().limit(3) → [1,2,3] |
skip(long) | 跳過前 N 個元素 | list.stream().skip(3) → [4,5,6] |
peek(Consumer) | 遍歷元素(無修改,常用于調(diào)試) | list.stream().peek(System.out::println).count() → 打印所有元素后返回計(jì)數(shù) |
3. 終端操作(觸發(fā)計(jì)算)
終端操作會消耗 Stream,返回非 Stream 結(jié)果(或無返回),常用操作如下:
(1)遍歷與消費(fèi)
forEach(Consumer):遍歷元素(最常用,有副作用);forEachOrdered(Consumer):并行流中保證遍歷順序(串行流與forEach無區(qū)別)。List<String> list = Arrays.asList("a", "b", "c"); list.stream().forEach(System.out::println); // 輸出 a b c(并行流可能亂序) list.parallelStream().forEachOrdered(System.out::println); // 保證順序 a b c
(2)聚合統(tǒng)計(jì)(返回單個值)
| 操作 | 功能描述 | 示例 |
|---|---|---|
count() | 返回元素個數(shù)(long 類型) | Stream.of(1,2,3).count() → 3 |
max(Comparator) | 返回最大值(Optional 類型,避免空指針) | Stream.of(1,2,3).max(Integer::compare) → Optional[3] |
min(Comparator) | 返回最小值 | Stream.of(1,2,3).min(Integer::compare) → Optional[1] |
findFirst() | 返回第一個元素(Optional,串行流高效) | Stream.of(1,2,3).findFirst() → Optional[1] |
findAny() | 返回任意一個元素(并行流高效) | Stream.of(1,2,3).parallel().findAny() → 可能是 1/2/3 |
anyMatch(Predicate) | 是否存在滿足條件的元素(短路求值) | Stream.of(1,2,3).anyMatch(n -> n > 2) → true |
allMatch(Predicate) | 所有元素是否滿足條件(短路求值) | Stream.of(1,2,3).allMatch(n -> n > 0) → true |
noneMatch(Predicate) | 所有元素是否都不滿足條件(短路求值) | Stream.of(1,2,3).noneMatch(n -> n < 0) → true |
短路求值:一旦滿足條件就停止計(jì)算(如 anyMatch 找到一個滿足條件的元素就返回,無需遍歷所有)。
(3)收集結(jié)果(collect(Collector))
將 Stream 結(jié)果收集為集合、數(shù)組、Map 等,由Collectors 工具類提供了大量預(yù)定義收集器。
① 收集為集合
List<Integer> list = Stream.of(1,2,3).collect(Collectors.toList()); // 默認(rèn) ArrayList List<Integer> linkedList = Stream.of(1,2,3).collect(Collectors.toCollection(LinkedList::new)); // 指定集合 Set<Integer> set = Stream.of(1,2,2).collect(Collectors.toSet()); // 去重集合
② 收集為數(shù)組
Integer[] arr = Stream.of(1,2,3).toArray(Integer[]::new); // 推薦(類型安全) Object[] objArr = Stream.of(1,2,3).toArray(); // 不推薦(需要強(qiáng)轉(zhuǎn))
③ 收集為 Map(注意 key 唯一)
List<String> userList = Arrays.asList("id1:張三", "id2:李四", "id3:王五");
// key: 拆分后的id,value: 拆分后的姓名
Map<String, String> userMap = userList.stream()
.map(user -> user.split(":")) // 拆分后為 String[]
.collect(Collectors.toMap(
arr -> arr[0], // key 映射
arr -> arr[1] // value 映射
));
// 若 key 可能重復(fù),需指定沖突解決策略(如保留第一個)
Map<String, String> userMapSafe = userList.stream()
.map(user -> user.split(":"))
.collect(Collectors.toMap(
arr -> arr[0],
arr -> arr[1],
(v1, v2) -> v1 // 沖突時保留第一個值
));④ 聚合統(tǒng)計(jì)(求和、平均值等)
List<Integer> numList = Arrays.asList(1,2,3,4,5);
// 求和(Integer 類型)
int sum = numList.stream().collect(Collectors.summingInt(Integer::intValue));
// 求和(Long 類型,避免溢出)
long sumLong = numList.stream().collect(Collectors.summingLong(Integer::longValue));
// 平均值(返回 Double)
double avg = numList.stream().collect(Collectors.averagingInt(Integer::intValue));
// 統(tǒng)計(jì)信息(總和、平均值、計(jì)數(shù)等)
IntSummaryStatistics stats = numList.stream().collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("總和:" + stats.getSum());
System.out.println("平均值:" + stats.getAverage());
System.out.println("最大值:" + stats.getMax()); ⑤ 分組(groupingBy)
按指定條件分組,返回 Map<分組鍵, List<元素>>:
List<String> fruitList = Arrays.asList("蘋果", "香蕉", "橙子", "紅蘋果", "青蘋果");
// 按首字符分組
Map<Character, List<String>> groupByFirstChar = fruitList.stream()
.collect(Collectors.groupingBy(fruit -> fruit.charAt(0)));
// 結(jié)果:{蘋=[蘋果, 紅蘋果, 青蘋果], 香=[香蕉], 橙=[橙子]}
// 分組后統(tǒng)計(jì)個數(shù)(返回 Map<分組鍵, 計(jì)數(shù)>)
Map<Character, Long> countByGroup = fruitList.stream()
.collect(Collectors.groupingBy(
fruit -> fruit.charAt(0),
Collectors.counting() // 下游收集器:統(tǒng)計(jì)個數(shù)
));
// 結(jié)果:{蘋=3, 香=1, 橙=1} ⑥ 拼接字符串(joining)
List<String> strList = Arrays.asList("a", "b", "c");
String join1 = strList.stream().collect(Collectors.joining()); // "abc"
String join2 = strList.stream().collect(Collectors.joining(",")); // "a,b,c"
String join3 = strList.stream().collect(Collectors.joining(",", "[", "]")); // "[a,b,c]"五、并行
Stream 天生支持并行,只需將串行流轉(zhuǎn)為并行流,無需手動處理線程:
- 集合直接獲取并行流:
list.parallelStream(); - 串行流轉(zhuǎn)為并行流:
stream.parallel()。
注意:
- 線程安全:若中間操作修改外部變量(如全局集合),需保證線程安全(建議使用無狀態(tài)操作,避免副作用);
- 順序性:并行流的
forEach可能亂序,需順序則用forEachOrdered(但會降低并行效率); - 性能考量:數(shù)據(jù)量小時,并行流的線程開銷可能超過收益(建議數(shù)據(jù)量 > 1000 時使用)
- 處理效率:數(shù)據(jù)量越大,并行流優(yōu)勢越明顯(小數(shù)據(jù)量可能因線程開銷導(dǎo)致效率更低)。
- 非線程安全的數(shù)據(jù)源:如
ArrayList是線程安全的(讀取時),但LinkedList并行效率低,建議使用數(shù)組或ArrayList作為并行流數(shù)據(jù)源。
示例:
List<Integer> numList = IntStream.range(1, 1000000).boxed().collect(Collectors.toList()); // 串行流求和(耗時較長) long serialSum = numList.stream().mapToLong(Integer::longValue).sum(); // 并行流求和(效率更高) long parallelSum = numList.parallelStream().mapToLong(Integer::longValue).sum();
六、注意
- 忘記終端操作:只寫中間操作不寫終端操作,代碼不會執(zhí)行;
- 重復(fù)使用 Stream:一個 Stream 執(zhí)行終端操作后關(guān)閉,再次使用會拋出
IllegalStateException; - 并行流的副作用:并行流中修改外部非線程安全的變量(如
ArrayList),會導(dǎo)致數(shù)據(jù)錯亂; - 濫用
peek:peek設(shè)計(jì)用于調(diào)試,不建議用于修改元素(雖然語法允許); - 忽略
Optional空指針風(fēng)險(xiǎn):max、findFirst等返回Optional,直接調(diào)用get()可能拋出NoSuchElementException,建議用orElse()或ifPresent()處理空值:
// 錯誤:無元素時拋出異常 Integer max = Stream.empty().max(Integer::compare).get(); // 正確:無元素時返回默認(rèn)值 0 Integer maxSafe = Stream.empty().max(Integer::compare).orElse(0);
七、總結(jié)
Stream 是 Java 處理集合數(shù)據(jù)的利器,特點(diǎn)總結(jié):
- 簡潔:用鏈?zhǔn)秸{(diào)用替代嵌套循環(huán),代碼可讀性高;
- 高效:惰性求值 + 并行支持,處理大數(shù)據(jù)更高效;
- 靈活:豐富的中間操作和終端操作,覆蓋大部分?jǐn)?shù)據(jù)處理場景。
實(shí)際開發(fā)中,建議用 Stream 替代傳統(tǒng)的 for 循環(huán)和 Iterator,尤其是處理復(fù)雜數(shù)據(jù)轉(zhuǎn)換、過濾、統(tǒng)計(jì)時,能大幅提升開發(fā)效率和代碼質(zhì)量。
到此這篇關(guān)于Java中的Stream流式操作大全的文章就介紹到這了,更多相關(guān)java stream流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?IoC容器Bean作用域的singleton與prototype使用配置
這篇文章主要為大家介紹了Spring?IoC容器Bean作用域的singleton與prototype使用配置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Spring Boot 分布式鎖與并發(fā)控制的應(yīng)用場景
本章主要介紹了SpringBoot分布式鎖與并發(fā)控制的核心概念與使用方法,包括分布式鎖的定義與特點(diǎn)、并發(fā)控制的定義與特點(diǎn)、SpringBoot與分布式鎖的集成、SpringBoot的實(shí)際應(yīng)用場景,感興趣的朋友跟隨小編一起看看吧2026-03-03
Java工具類實(shí)現(xiàn)高效編寫報(bào)表
對于報(bào)表數(shù)據(jù)大部分情況下使用寫sql的方式為大屏/報(bào)表提供數(shù)據(jù)來源,但是對于某些復(fù)雜情況下僅僅使用sql無法實(shí)現(xiàn),這篇文章主要介紹了Java工具類實(shí)現(xiàn)高效編寫報(bào)表2022-11-11
基于Spring p標(biāo)簽和c標(biāo)簽注入方式
這篇文章主要介紹了Spring p標(biāo)簽和c標(biāo)簽注入方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
基于SpringBoot + MyBatis-Plus高效實(shí)現(xiàn)數(shù)據(jù)變更記錄
在應(yīng)用開發(fā)過程中,在某些情況下,需要實(shí)現(xiàn)數(shù)據(jù)的變更記錄,以便于在未來進(jìn)行數(shù)據(jù)操作變更的回溯,那有什么辦法高效實(shí)現(xiàn)數(shù)據(jù)操作變更記錄,而不需要一個一個地方去加呢?答案當(dāng)然是有,今天小編就給大家介紹下如何基于SpringBoot + MyBatis-Plus高效實(shí)現(xiàn)數(shù)據(jù)的變更記錄,2026-02-02
解析ConcurrentHashMap: 紅黑樹的代理類(TreeBin)
ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu),今天給大家普及java面試常見問題---ConcurrentHashMap知識,一起看看吧2021-06-06
Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問題詳解
這篇文章主要介紹了Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問題詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

