一、??典型解析
1.1 ??Java 8中的Stream 都能做什么
Stream 使用一種類似用 SOL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對Java 集合運(yùn)算和表達(dá)的高階抽象。
Stream API
可以極大提高Java程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡潔的代碼。
這種風(fēng)格將要處理的元素集合看作一種流,流在管道中傳輸,并且可以在管道的節(jié)點(diǎn)上進(jìn)行處理,比如篩選,排序,聚合等。
Stream有以下特性及優(yōu)點(diǎn):
-
無存儲。Stream不是一種數(shù)據(jù)結(jié)構(gòu),它只是某種數(shù)據(jù)源的一個(gè)視圖,數(shù)據(jù)源可以是一個(gè)數(shù)組,
Java容器
或I/O channel
等。 -
為函數(shù)式編程而生。對
Stream
的任何修改都不會修改背后的數(shù)據(jù)源,比如對Stream執(zhí)行過濾操作并不會刪除被過濾的元素,而是會產(chǎn)生一個(gè)不包含被過濾元素的新Stream。 - 惰式執(zhí)行。Stream上的操作并不會立即執(zhí)行,只有等到用戶真正需要結(jié)果的時(shí)候才會執(zhí)行。
- 可消費(fèi)性。Stream只能被“消費(fèi)”一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要重次遍歷必須重新生成。
我們舉一個(gè)例子,來看一下到底Stream可以做什么事情:
上面的例子中,獲取一些帶顏色塑料球作為數(shù)據(jù)源,首先過濾掉紅色的、把它們?nèi)诨呻S機(jī)的三角形。再過濾器并刪除小的三角形。最后計(jì)算出剩余圖形的周長。
如上圖,對于流的處理,主要有三種關(guān)鍵性操作: 分別是流的創(chuàng)建
、中間操作 (intermediateoperation)
以及最終操作(terminal operation)
。
1.2 ??Stream的創(chuàng)建
在Java 8中,可以有多種方法來創(chuàng)建流。
1、通過已有的集合來創(chuàng)建流
在Java 8中,除了增加了很多Stream相關(guān)的類以外,還對集合類自身做了增強(qiáng),在其中增加了stream方法,可以將一個(gè)集合類轉(zhuǎn)換成流。
List<String> strings = Arrays.asList("Java", "JavaAndJ", "Java", "Hello", "HelloWorld","Java");
Stream<String> stream = strings.stream();
以上,通過一個(gè)已有的List創(chuàng)建一個(gè)流。除此以外,還有一個(gè)parallelStream
方法,可以為集合創(chuàng)建個(gè)并行流。
這種通過集合創(chuàng)建出一個(gè)Stream的方式也是比較常用的一種方式。
2、通過Stream創(chuàng)建流
可以使用Stream類提供的方法,直接返回一個(gè)由指定元素組成的流。
Stream<String> stream = Stream.of("Java", "JavaAndJ", "Java", "Hello", "HelloWorld","Java");
如以上代碼,直接通過 of
方法,創(chuàng)建并返回一個(gè)Stream。
二、? Stream中間操作
Stream有很多中間操作,多個(gè)中間操作可以連接起來形成一個(gè)流水線,每一個(gè)中間操作就像流水線上的一個(gè)工人,每人工人都可以對流進(jìn)行加工,加工后得到的結(jié)果還是一個(gè)流。
以下是常用的中間操作列表:
2.1 ??Filter
Stream的中間操作Filter的作用是過濾流中的元素,它接受一個(gè)Predicate接口作為參數(shù),該接口中的test方法可以對給定的參數(shù)進(jìn)行判斷,返回一個(gè)布爾值。通過調(diào)用filter方法,我們可以從流中過濾出滿足條件的元素。
在Java中,我們可以通過以下步驟使用Filter中間操作:
1. 創(chuàng)建Stream對象,對需要過濾的集合對象使用stream().filter(Predicate)方法。
2. 傳入一個(gè)Lambda表達(dá)式作為參數(shù),該Lambda表達(dá)式定義了過濾條件。
3. 執(zhí)行終止操作,如collect、forEach等,將過濾后的結(jié)果輸出或處理。
看一個(gè)Demo:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 演示了如何使用Java Stream API進(jìn)行一系列的中間操作和終止操作
* 來處理一個(gè)學(xué)生列表,包括篩選特定條件的學(xué)生、統(tǒng)計(jì)滿足條件的
* 學(xué)生數(shù)量、計(jì)算平均分
*/
public class StreamComplexExample {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 90),
new Student("Charlie", 78),
new Student("David", 88),
new Student("Eve", 92)
);
// 篩選出平均分大于等于85的學(xué)生,并計(jì)算滿足條件的學(xué)生數(shù)量
int count = students.stream()
.filter(student -> student.getScore() >= 85)
.collect(Collectors.toList())
.size();
System.out.println("滿足條件的學(xué)生數(shù)量: " + count); // 輸出: 3
// 計(jì)算平均分大于等于85的學(xué)生的平均分
double averageScore = students.stream()
.filter(student -> student.getScore() >= 85)
.mapToDouble(Student::getScore) // 將學(xué)生對象轉(zhuǎn)換為分?jǐn)?shù)
.average() // 計(jì)算平均分
.orElse(0); // 如果流為空,返回0
System.out.println("平均分: " + averageScore); // 輸出: 88.33333333333334
// 根據(jù)性別分組,并計(jì)算每個(gè)性別的平均分
Map<String, Double> averageScores = students.stream()
.collect(Collectors.groupingBy(Student::getGender,
Collectors.averagingDouble(Student::getScore)));
System.out.println("平均分(按性別): " + averageScores);
// 輸出: {男=87.5, 女=86}
}
}
2.2 ??Map
Stream的中間操作Map的作用是將流中的每個(gè)元素轉(zhuǎn)換成另一個(gè)對象,并返回一個(gè)新的流。Map操作接受一個(gè)Function接口作為參數(shù),該接口中的apply方法可以對給定的參數(shù)進(jìn)行轉(zhuǎn)換,并返回轉(zhuǎn)換后的結(jié)果。
在Java中,我們可以通過以下步驟使用Map中間操作:
- 創(chuàng)建Stream對象,對需要映射的集合對象使用
stream().map(Function)
方法。 - 傳入一個(gè)Lambda表達(dá)式或方法引用作為參數(shù),該Lambda表達(dá)式或方法引用定義了映射規(guī)則。
- 執(zhí)行終止操作,如collect、forEach等,將映射后的結(jié)果輸出或處理。
Demo:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 使用Java Stream API進(jìn)行一系列的中間操作和終止操作來處理一個(gè)
* 整數(shù)列表,包括過濾出偶數(shù)、計(jì)算每個(gè)偶數(shù)的平方、以及找出平方值
* 最大的那個(gè)偶數(shù)
*/
public class StreamComplexExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 過濾出偶數(shù),并計(jì)算每個(gè)偶數(shù)的平方
List<Integer> squaredEvens = numbers.stream()
.filter(number -> number % 2 == 0)
.map(number -> number * number)
.collect(Collectors.toList());
System.out.println("平方的偶數(shù): " + squaredEvens); // 輸出: [4, 16, 36, 64]
// 找出平方值最大的那個(gè)偶數(shù)
Optional<Integer> maxSquare = squaredEvens.stream()
.max(Integer::compare);
if (maxSquare.isPresent()) {
System.out.println("平方值最大的偶數(shù): " + maxSquare.get()); // 輸出: 64
} else {
System.out.println("沒有找到平方值最大的偶數(shù)");
}
}
}
// 輸出結(jié)果:
// 平方的偶數(shù): [4, 16, 36, 64]
// 平方值最大的偶數(shù): 64
2.3 ??limit / skip
在Java的Stream API中,limit
是一個(gè)中間操作,用于限制流中的元素?cái)?shù)量。這個(gè)操作返回一個(gè)新的流,其中只包含原始流中的前N個(gè)元素。
Demo:
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 如何使用Java Stream API進(jìn)行一系列的中間操作和終止操作來處理
* 一個(gè)字符串列表,包括將字符串轉(zhuǎn)換為小寫、過濾出長度大于等于5的
* 字符串、計(jì)算每個(gè)字符串的長度、找出長度最大的字符串
*/
public class StreamComplexExample {
public static void main(String[] args) {
List<String> strings = Arrays.asList("Hello", "World", "Java", "Stream", "Example");
// 將字符串轉(zhuǎn)換為小寫,并過濾出長度大于等于5的字符串
List<String> lowercaseStrings = strings.stream()
.map(String::toLowerCase) // 將字符串轉(zhuǎn)換為小寫
.filter(string -> string.length() >= 5) // 過濾出長度大于等于5的字符串
.collect(Collectors.toList()); // 收集結(jié)果到列表
System.out.println("Lowercase strings: " + lowercaseStrings); // 輸出: [hello, world, example]
// 計(jì)算每個(gè)字符串的長度,并找出長度最大的字符串
Optional<String> longestString = lowercaseStrings.stream()
.max(String::compareTo); // 比較字符串長度
if (longestString.isPresent()) {
System.out.println("Longest string: " + longestString.get()); // 輸出: Example
} else {
System.out.println("No longest string found");
}
}
}
//輸出結(jié)果:
//Lowercase strings: [hello, world, example]
//Longest string: Example
2.4 ??sorted
在Java的Stream API中,sorted是一個(gè)中間操作,用于對流中的元素進(jìn)行排序。它可以按照自然順序進(jìn)行排序,也可以根據(jù)指定的比較器進(jìn)行排序。
Demo:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 使用Java Stream API的`sorted`中間操作對整數(shù)列表進(jìn)行排序
*/
public class StreamSortedIntegerExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
// 使用sorted中間操作對整數(shù)進(jìn)行排序
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 輸出: [1, 2, 5, 7, 9]
// 如果你想降序排序,可以使用Comparator.reverseOrder()
List<Integer> sortedDescending = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedDescending); // 輸出: [9, 7, 5, 2, 1]
}
}
注意:
sorted
方法不會修改原始流中的元素,而是返回一個(gè)新的流,其中包含排序后的元素。要查看排序結(jié)果,需要使用一個(gè)終止操作(在這個(gè)例子中是collect
)來收集流中的元素。
2.5 ??distinct
在Java的Stream API中,distinct
是一個(gè)中間操作,用于去除流中的重復(fù)元素。
Demo:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 使用`distinct`中間操作
*/
public class StreamDistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
// 使用distinct中間操作去除重復(fù)元素
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers); // 輸出: [1, 2, 3, 4, 5]
}
}
例子中,創(chuàng)建了一個(gè)包含七個(gè)整數(shù)的列表numbers
,其中有兩個(gè)2和兩個(gè)4。然后,我們使用stream().distinct()
將流中的重復(fù)元素去除,并將結(jié)果收集到一個(gè)新的列表distinctNumbers
中。最后,我們打印出這個(gè)去重后的列表。
接下來我們通過一個(gè)例子和一張圖,來演示下,當(dāng)一個(gè)Stream先后通過filter、map、sort、limit以及distinct處理后會發(fā)生什么。
代碼如下:
List<String> strings = Arrays.aslist("Java", "JavaAndJ", "Java", "Hello", "HelloWorld","Java");
Stream s = strings.stream(),filter(string -> string.length( )<= 6)
.map(string::length).sorted()
.limit(3)
.distinct();
過程及每一步得到的結(jié)果我已經(jīng)給大家畫出來了,幫助大家快速掌握:
三、 ?Stream最終操作
Stream的中間操作得到的結(jié)果還是一個(gè)Stream,那么如何把一個(gè)Stream轉(zhuǎn)換成我們需要的類型呢? 比如計(jì)算出流中元素的個(gè)數(shù)、將流裝換成集合等。這就需要最終操作
(terminal operation)
最終操作會消耗流,產(chǎn)生一個(gè)最終結(jié)果。也就是說,在最終操作之后,不能再次使用流,也不能在使用任何中間操作,否則將拋出異常:
java.lang.IllegalStateException: stream has already been operated upon or closed
俗話說,“你永遠(yuǎn)不會兩次踏入同一條河” 也正是這個(gè)意思。
常用的最終操作如下圖:
3.1 ??forEach
Stream 提供了方法 "forEach’來迭代流中的每個(gè)數(shù)據(jù)。以下代碼片段使用 forEach 輸出了10個(gè)隨機(jī)數(shù):
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
3.2 ??count
count用來統(tǒng)計(jì)流中的元素個(gè)數(shù)。
List<String> strings = Arrays.aslist("Java", "JavaAndJ","Java666", "Java", "Hello", "HelloWorld","Java");
System.out.printIn(strings.stream().count()); //7
3.3 ??collect
List<String> strings = Arrays.asList ("Java", "JavaAndJ","Java666", "Java", "Hello", "HelloWorld","Java");
strings = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.tolist());
System.out.println(strings);
接下來,我們還是使用一張圖,來演示下,前文的例子中,當(dāng)一個(gè)Stream先后通過filter、map.sort、limit以及distinct處理后會,在分別使用不同的最終操作可以得到怎樣的結(jié)果。
下圖,展示了文中介紹的所有操作的位置、輸入、輸出以及使用一個(gè)案例展示了其結(jié)果:
四、? 擴(kuò)展知識倉
4.1 ??Stream有哪些優(yōu)點(diǎn)和缺點(diǎn)
Stream的優(yōu)點(diǎn):
- 聲明性:Stream允許我們以聲明性的方式處理數(shù)據(jù),這意味著我們只需關(guān)注需要做什么,而不是如何做。這使得代碼更加簡潔和易讀。
- 可復(fù)合性:Stream的操作是可復(fù)合的,這意味著多個(gè)操作可以鏈?zhǔn)秸{(diào)用,從而簡化了代碼。
- 可并行處理:Stream操作可以并行執(zhí)行,這使得處理大量數(shù)據(jù)更加高效。
- 延遲計(jì)算:Stream操作在需要結(jié)果時(shí)才執(zhí)行,這使得計(jì)算更加高效。
- 操作轉(zhuǎn)換:Stream提供了一種簡單而強(qiáng)大的方式來對數(shù)據(jù)進(jìn)行過濾、轉(zhuǎn)換、排序和聚合等操作。
Stream缺點(diǎn):
- 數(shù)據(jù)不可變性:Stream操作返回的是新的Stream對象,而不是修改原始數(shù)據(jù)集。這意味著每次操作都會創(chuàng)建一個(gè)新的數(shù)據(jù)集,這可能會導(dǎo)致內(nèi)存和性能問題,特別是對于大規(guī)模數(shù)據(jù)集。
- 錯誤處理:當(dāng)Stream操作發(fā)生錯誤時(shí),需要特別小心處理。由于Stream操作是惰性計(jì)算的,一旦發(fā)生錯誤,可能需要重新計(jì)算整個(gè)數(shù)據(jù)集才能找到問題所在。
- 類型安全:在使用Stream時(shí),需要特別注意類型安全。由于Stream操作是惰性計(jì)算的,類型信息在計(jì)算過程中可能會丟失,從而導(dǎo)致類型錯誤。
- 函數(shù)式編程的限制:Stream是基于函數(shù)式編程的,這使得代碼更加簡潔和易讀。但是,這也意味著某些傳統(tǒng)的編程模式可能不適用。例如,循環(huán)和條件語句在某些情況下可能比使用Stream更簡單和直觀。
4.2 ??Stream中間操作的作用是什么
Stream中間操作在Java中扮演著處理數(shù)據(jù)流的重要角色。它們的主要作用是在數(shù)據(jù)流上進(jìn)行一系列轉(zhuǎn)換和操作,以產(chǎn)生一個(gè)新的數(shù)據(jù)流供后續(xù)操作使用。中間操作不會立即執(zhí)行實(shí)際的數(shù)據(jù)處理,而是創(chuàng)建一個(gè)新的流,并在其中定義將要執(zhí)行的操作。這種特性被稱為“惰性求值”或“延遲執(zhí)行”,意味著只有在終端操作被觸發(fā)時(shí),中間操作才會真正開始執(zhí)行。
中間操作可以包括過濾、映射、排序、去重等各種轉(zhuǎn)換操作。例如,使用filter
方法可以根據(jù)給定的條件過濾流中的元素,只保留滿足條件的元素。map
方法則用于對流中的每個(gè)元素應(yīng)用一個(gè)函數(shù),將其轉(zhuǎn)換成另一種形式。這些中間操作可以鏈?zhǔn)秸{(diào)用,形成一個(gè)處理管道,對流進(jìn)行多次轉(zhuǎn)換。
通過中間操作,可以逐步構(gòu)建和調(diào)整數(shù)據(jù)流,以滿足特定的處理需求。最終,通過終端操作(如collect
、forEach
等)觸發(fā)實(shí)際的數(shù)據(jù)處理,并產(chǎn)生最終結(jié)果或副作用。這種聲明性的處理方式使得代碼更加簡潔、易讀,并有助于提高代碼的可維護(hù)性和可重用性。
4.3 ??Stream終極操作的作用是什么
Stream的終極操作會消耗流并產(chǎn)生一個(gè)最終結(jié)果。這意味著一旦進(jìn)行終極操作,就不能再次使用流,也不再使用任何中間操作,否則會拋出異常。常見的終極操作包括計(jì)算出流中元素的個(gè)數(shù)、將流轉(zhuǎn)換成集合、以及遍歷流中的元素等。
終極操作的主要作用是完成數(shù)據(jù)流的處理,并產(chǎn)生所需的結(jié)果。例如,通過collect
操作將流轉(zhuǎn)換為集合,或使用forEach
操作對每個(gè)元素執(zhí)行某些操作。一旦執(zhí)行了終極操作,流中的數(shù)據(jù)就被處理完畢,并且流對象本身通常會被丟棄或不再使用。文章來源:http://www.zghlxwxcb.cn/news/detail-786165.html
注意:在Java中,如果流支持迭代器(
iterator
),那么在調(diào)用iterator()
方法后,流就不能再被使用,否則會拋出IllegalStateException
異常。這是因?yàn)榈鲿牧髦械脑?,并且不支持逆向操作。因此,在使用迭代器時(shí),必須小心處理流的剩余部分,以避免潛在的錯誤和異常。文章來源地址http://www.zghlxwxcb.cn/news/detail-786165.html
到了這里,關(guān)于【昕寶爸爸小模塊】深入淺出之Java 8中的 Stream的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!