Java的Stream入門級教程
什么是Stream流
Java 的 Stream(流)是從 Java 8 引入的一套面向集合數(shù)據(jù)的聲明式處理API。它讓你像寫“數(shù)據(jù)流水線”一樣,對一組元素做篩選、變換、聚合等操作,代碼更簡潔、更可讀。
核心特性
管道化:source → 中間操作* → 終止操作
惰性求值:只有遇到終止操作才真正執(zhí)行。
無副作用:鼓勵函數(shù)式寫法,避免改動外部可變狀態(tài)。
可并行:parallelStream() 能并行處理(適合大數(shù)據(jù)量且拆分成本低的場景)。
Stream流不是數(shù)據(jù)本身,而是數(shù)據(jù)的視圖,且Stream不可重復(fù)使用,且基于函數(shù)式接口
Stream流的生命周期
Java中Stream流的生命周期分三個步驟創(chuàng)建 → 中間操作 → 終止操作即生成數(shù)據(jù),處理數(shù)據(jù)到最后執(zhí)行結(jié)果
創(chuàng)建流
源:流是基于數(shù)據(jù)源如集合,數(shù)組,文件創(chuàng)建的,數(shù)據(jù)源可以是集合,數(shù)組,生成器等
例子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> stream = numbers.stream(); // 創(chuàng)建流
中間操作
惰性執(zhí)行:中間操作是惰性求值的,他們不會立即執(zhí)行。而是返回一個新的流,只有遇到終止操作的時候,中間操作才會執(zhí)行。
例子:
Stream<Integer> evenNumbers = stream.filter(n -> n % 2 == 0) // 中間操作
.map(n -> n * n); // 另一個中間操作
終止操作
觸發(fā)執(zhí)行:終止操作會觸發(fā)流的處理鏈執(zhí)行,一旦執(zhí)行終止操作,流就會被消耗掉,而不能再使用.終止操作是流處理的真正的啟動器,他會返回一個最終的結(jié)果
int sum = evenNumbers.reduce(0, Integer::sum); // 終止操作,執(zhí)行計算
流的關(guān)閉
一旦流被消耗掉就不能再被重新使用,當(dāng)要再次處理數(shù)據(jù)的時候就要創(chuàng)建一個新的流
在關(guān)閉流的資源上對于集合類的流就不需要顯示關(guān)閉但是對于文件流,網(wǎng)絡(luò)流等最好通過close()來關(guān)閉
Stream流的創(chuàng)建方式
list.stream() | 從集合創(chuàng)建 |
| Arrays.stream(array) | 從數(shù)組創(chuàng)建 |
| Stream.of("A","B","C") | 從值創(chuàng)建 |
| Files.lines(Path.get("file.txt")) | 從文件行創(chuàng)建 |
| Stream.iterate(0,n->n+1),Stream.generate(Math::random) | 無限創(chuàng)建流 |
常見的中間操作
| filter(): | 過濾符合的條件 | stream.filter(s -> s.length() > 3) |
| map(Function) | 轉(zhuǎn)換每個元素或者是提取對象的某個是屬性。將原始流中的每個元素映射為另一個類型或值。 | 例子:steam.map(String::toUpperCase) |
| flatMap(Function) | 扁平化處理,用于嵌套結(jié)構(gòu) | 例子:stream.flatMap(List::stream) |
| distinct() | 去重(基于equals()和hashCode()方法) | 例子:stream.distinct() |
| sorted() | 排序(自然排序或者是自定義排序) | 例子:stream.sorted(Comparator.reverseOrder()) |
| liit(n) | 截取前n個元素 | 例子:stream.limit(10) |
skip(n) | 跳過前n個元素 | stream.skip(5) |
:
:
:
常用的終端操作
| forEach(Consumer) | 遍歷每個元素 | stream.forEach(System.out::println) |
| collect(Collector) | 收集結(jié)果(轉(zhuǎn)為 List、Set、Map 等) | stream.collect(Collectors.toList()) |
| count() | 返回元素個數(shù) | stream.count() |
| anyMatch(Predicate) | 是否有任意一個匹配 | stream.anyMatch(s -> s.contains("Java")) |
| allMatch(Predicate) | 是否全部匹配 | stream.allMatch(s -> s.length() > 3) |
| noneMatch(Predicate) | 是否沒有匹配 | stream.noneMatch(s -> s.isEmpty()) |
| findFirst() | 獲取第一個元素 | stream.findFirst() |
| reduce(BinaryOperator) | 聚合操作(如求和) | stream.reduce((a, b) -> a + b) |
注意:中間操作可以有多個,但是在一個stream流中流的創(chuàng)建和終止操作只能有一個
實戰(zhàn)用例
1.將一個對象列表中大于10歲的對象的名字提取出來
首先給定一個Person類(在實際開發(fā)中不建議將屬性定義為public,這里只是為了演示)
public class Person {
public String name;
public int age;
public String city;
public Person(String name,int age,String city){
this.name = name;
this.age = age;
this.city = city;
}
}
代碼演示:
import java.util.List;
import java.util.stream.Collectors;
public class StreamTest {
public static void main(String []str){
List<Person> people = List.of(
new Person("Alice", 23, "Beijing"),
new Person("Bob", 17, "Shanghai"),
new Person("Carol", 30, "Beijing")
);
//提取大于18歲的對象的名字
List<String> collect = people.stream()//獲取源數(shù)據(jù)
.filter(p -> p.age > 18)//過濾源數(shù)據(jù),滿足條件則true則就保留
.map(person -> person.name)//做map映射就是將這個源數(shù)據(jù)中的每個數(shù)據(jù)轉(zhuǎn)換成另外一種數(shù)據(jù),
// 在這里面就是將每個對象轉(zhuǎn)換成他們對應(yīng)的名字
.collect(Collectors.toList());//收集處理后的數(shù)據(jù),通過列表的方式
System.out.println(collect);
}
}2.分組統(tǒng)計,將列表中不同城市的人的數(shù)量做統(tǒng)計
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamTest {
public static void main(String []str){
List<Person> people = List.of(
new Person("Alice", 23, "Beijing"),
new Person("Bob", 17, "Shanghai"),
new Person("Carol", 30, "Beijing"),
new Person("Dave", 25, "Shanghai"),
new Person("Eve", 22, "Beijing")
);
Map<String, Long> collect = people.stream()//轉(zhuǎn)為流數(shù)據(jù)
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
//這個的作用是將這個流中的數(shù)據(jù)做一個分組,key是city,value是key對應(yīng)的數(shù)量Collectors.counting() 是一個下游收集器,用來統(tǒng)計分組后的元素數(shù)量。
//它返回一個 Long 類型的值,表示每個分組中元素的數(shù)量。在這里,它會統(tǒng)計每個城市中有多少個 Person 對象。
System.out.println(collect);
}
}3.多級分組-分區(qū)(在城市分組的情況下判斷是否成年)
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamTest {
public static void main(String []str){
List<Person> people = List.of(
new Person("Alice", 23, "Beijing"),
new Person("Bob", 17, "Shanghai"),
new Person("Carol", 30, "Beijing"),
new Person("Dave", 25, "Shanghai"),
new Person("Eve", 22, "Beijing")
);
Map<String, Map<Boolean, List<Person>>> collect = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.partitioningBy(p -> p.age > 18)));
//這里面的Collectors.groupingBy是將收集的數(shù)據(jù)進(jìn)行分組根據(jù)city屬性,他收集出來就是一個map,但是這個map的value不是一個值
//而是一個對象這個對象是一個分區(qū)的函數(shù)partitioningBy他會將value又分成一個map將滿足條件的放一組,不滿足的放一組來形成一個map
System.out.println(collect);
}4.聚合reduce
在java中的stream流的reduce方法是一個游泳的規(guī)約操作,將流中的元素反復(fù)結(jié)合起來最后得到一個值
reduce求和
import java.util.Arrays;
import java.util.List;
public class StreamTest {
public static void main(String []str){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b); // 0 是初始值,(a, b) -> a + b 是累加器
System.out.println(sum); // 輸出 15
}
}reduce求最大值
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream()
.reduce(Integer::max); // 使用 Integer::max 方法引用
max.ifPresent(System.out::println); // 輸出 5
在求最大值的時候就不需要初始值了
reduce拼接字符串
List<String> words = Arrays.asList("Java", "is", "awesome");
String result = words.stream()
.reduce("", (a, b) -> a + " " + b); // 初始值為空字符串
System.out.println(result); // 輸出 "Java is awesome"
5.統(tǒng)計年齡總數(shù)
IntSummaryStatistics stats = people.stream()
.collect(Collectors.summarizingInt(Person::age));
// stats.getCount()/getSum()/getMin()/getMax()/getAverage()
6.自定義排序
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamTest {
public static void main(String []str){
List<Person> people = List.of(
new Person("Alice", 23, "Beijing"),
new Person("Bob", 17, "Shanghai"),
new Person("Carol", 30, "Beijing"),
new Person("Dave", 25, "Shanghai"),
new Person("Eve", 22, "Beijing")
);
Comparator<Person> byCityThenAge = Comparator
.comparing(Person::getCity, Comparator.nullsLast(String::compareTo))//定義比較類先是通過city排序,對于null的排到最后
.thenComparingInt(Person::getAge);//然后通過年齡進(jìn)行比較
List<Person> sorted = people.stream()
.sorted(byCityThenAge)//通過比較類進(jìn)行排序
.collect(Collectors.toList());
System.out.println(sorted);
}
}怎么使用Comparator
Comparator 是 Java 中用于比較對象的接口,常用于排序操作。它定義了如何對對象進(jìn)行排序,并且可以自定義排序的規(guī)則??梢酝ㄟ^ Comparator 來指定對象之間的比較邏輯,并且與 Collections.sort() 或 Stream.sorted() 等方法配合使用。
Comparator接口常用方法:
compare(T o1, T o2):比較兩個對象o1和o2,返回一個整數(shù),通常為:- 負(fù)整數(shù):表示
o1小于o2 - 零:表示
o1等于o2 - 正整數(shù):表示
o1大于o2
- 負(fù)整數(shù):表示
reversed():返回一個逆序的比較器,反轉(zhuǎn)當(dāng)前比較器的排序順序。thenComparing(Comparator<? super T> other):鏈?zhǔn)秸{(diào)用,可以在現(xiàn)有排序規(guī)則基礎(chǔ)上,添加一個新的排序規(guī)則,用于次級排序(如果主排序條件相等)。comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator):根據(jù)指定的鍵進(jìn)行排序,通常配合Function使用,從對象中提取出需要排序的字段。
多條件排序
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Alice", 30),
new Person("Bob", 20)
);
// 按名字排序,如果名字相同再按年齡排序
people.sort(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge));
逆序排序
// 按年齡降序排序 people.sort(Comparator.comparing(Person::getAge).reversed());
使用 Comparator.nullsLast() 處理 null 值
List<String> strings = Arrays.asList("apple", "banana", null, "cherry", null);
strings.sort(Comparator.nullsLast(Comparator.naturalOrder())); // 排序時,null 值排在最后
strings.forEach(System.out::println);
使用Comparing排序
// 按年齡排序,使用靜態(tài)方法 Comparator.comparing people.sort(Comparator.comparing(Person::getAge)); // 按年齡升序
使用Lambda表達(dá)式排序
// 使用 Lambda 表達(dá)式簡化比較邏輯 Collections.sort(people, (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge())); // 按年齡升序
Stream流的優(yōu)勢?
1.簡潔性和可讀性
- 更簡潔的代碼:Stream API 使用鏈?zhǔn)讲僮?,減少了顯式的循環(huán)和條件判斷,使代碼更簡潔、易懂。
- 函數(shù)式編程:通過
map、filter、reduce等函數(shù)式操作,代碼變得更具聲明性,表示你想要做什么,而不是如何做,從而提高了可讀性。
2.避免顯式的循環(huán)
- 不再手動管理迭代器:Stream API 隱藏了迭代的細(xì)節(jié),減少了手動編寫
for循環(huán)或Iterator的代碼。這讓代碼更直觀、更符合直覺。
3.并行化和性能優(yōu)化
- 并行流:Stream API 支持非常方便的并行處理,幾乎無需修改代碼就能讓程序利用多核處理器進(jìn)行并行計算,從而提高處理大量數(shù)據(jù)時的性能。
- 并行流優(yōu)勢:特別適合處理大規(guī)模數(shù)據(jù)集或計算密集型任務(wù),自動進(jìn)行任務(wù)分解和并行處理,無需開發(fā)者顯式管理線程池。
4.惰性計算和延遲求值
- 惰性求值:Stream 中的許多操作(如
filter,map,flatMap等)是惰性計算的,即只有當(dāng)終止操作(如collect,forEach,reduce)觸發(fā)時,流中的操作才會執(zhí)行。 - 只有當(dāng)
collect()被調(diào)用時,filter和map才會執(zhí)行,從而避免了中間計算的浪費(fèi)。
5.函數(shù)式編程思想
- 高階函數(shù):Stream API 使用了函數(shù)式編程的概念,支持高階函數(shù)(可以將函數(shù)作為參數(shù)傳遞),這使得代碼更加靈活、可復(fù)用。
6.增強(qiáng)的可組合性和復(fù)用性
- 組合多個操作:Stream API 允許你將多個操作組合成一個流水線,進(jìn)行一系列轉(zhuǎn)換、過濾、聚合等操作,這樣的鏈?zhǔn)秸{(diào)用使得代碼更具可組合性。
7.更好的錯誤處理
- 流的每一步都可自定義:Stream API 允許你對每一步進(jìn)行靈活的定制。例如,通過
try-catch語句、peek等方法,可以更清晰地處理流的操作過程中的異常。 - 你可以在流的操作過程中添加日志或調(diào)試信息,幫助更好地排查問題。
8.數(shù)據(jù)轉(zhuǎn)換和聚合的高級功能
- 復(fù)雜的數(shù)據(jù)轉(zhuǎn)換和聚合:Stream API 提供了非常強(qiáng)大的聚合功能(如
collect,reduce,groupingBy,partitioningBy,summarizingInt等),可以幫助你輕松處理復(fù)雜的數(shù)據(jù)轉(zhuǎn)換和聚合任務(wù)。
9.可維護(hù)性和擴(kuò)展性
- 代碼結(jié)構(gòu)清晰:使用 Stream API 可以讓數(shù)據(jù)處理代碼更為模塊化,容易理解,尤其是在面對復(fù)雜的過濾、映射、聚合等操作時。相對于傳統(tǒng)的多層循環(huán),Stream API 提供了更清晰的數(shù)據(jù)流轉(zhuǎn)過程,有助于后續(xù)的維護(hù)和擴(kuò)展。
- 提升代碼質(zhì)量:流式操作鼓勵無副作用的編程(即不修改原始數(shù)據(jù)),這減少了 bug 的發(fā)生概率,并且提高了代碼的可測試性。
10.內(nèi)存使用優(yōu)化
- 惰性加載:Stream 中的許多操作是惰性求值的,這意味著它們不會立即執(zhí)行,只有當(dāng)終止操作(如
collect)被調(diào)用時,才會開始處理數(shù)據(jù)。這有助于避免在某些情況下的內(nèi)存浪費(fèi),尤其是在處理大型數(shù)據(jù)集時。
總結(jié):Stream API 的優(yōu)勢
相比于傳統(tǒng)的開發(fā)方式,Java Stream API 提供了以下幾大優(yōu)勢:
- 簡潔性和可讀性:代碼更簡潔,避免了顯式的循環(huán)。
- 函數(shù)式編程支持:通過高階函數(shù)提供更高的靈活性和可重用性。
- 并行處理:輕松使用
parallelStream()實現(xiàn)并行處理,提高性能。 - 惰性計算和延遲求值:避免不必要的計算,提高性能。
- 豐富的聚合和轉(zhuǎn)換功能:提供強(qiáng)大的數(shù)據(jù)聚合、轉(zhuǎn)換、過濾等功能。
- 函數(shù)式思維:鼓勵不修改數(shù)據(jù)的不可變式操作。
到此這篇關(guān)于Java的Stream詳解的文章就介紹到這了,更多相關(guān)java stream內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中List轉(zhuǎn)Map List實現(xiàn)的幾種姿勢
本文主要介紹了Java中List轉(zhuǎn)Map List實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
Java基礎(chǔ)學(xué)習(xí)之運(yùn)算符相關(guān)知識總結(jié)
今天帶大家復(fù)習(xí)Java基礎(chǔ)知識,文中對Java運(yùn)算符相關(guān)知識作了詳細(xì)總結(jié),對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05
在Spring Boot中加載初始化數(shù)據(jù)的實現(xiàn)
這篇文章主要介紹了在Spring Boot中加載初始化數(shù)據(jù)的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02

