集合優(yōu)化了對象的存儲,而流則是對數(shù)據(jù)的處理
流是一系列與特定存儲機制無關(guān)的元素,利用流,我們無需迭代集合中的元素,就可以提取和操作它們。這些管道通常被組合在一起,在流上形成一條操作管道。
使用流的一個核心好處是,它使得程序更加短小并且更易理解。同時流還可以同 Lambda 表達式和方法引用(method references)一起使用。
流一個重要的特性是:流是懶加載的,意味著它在絕對必要的情況下才計算。這個特性是我們可以表示非常大(甚至無限)的序列,而不用擔心內(nèi)存的問題。
流的支持
Java 設(shè)計者在設(shè)計流時面臨著這樣一個難題:現(xiàn)存的大量類庫不僅為 Java 所用,同時也被應(yīng)用在整個 Java 生態(tài)圈數(shù)百萬行的代碼中。如何將一個全新的流的概念融入到現(xiàn)有類庫中呢?
Java 8 采用的解決方案是:在接口中添加被 default(默認)修飾的方法。通過這種方案,設(shè)計者們可以將流式(stream)方法平滑地嵌入到現(xiàn)有類中。流方法預(yù)置的操作幾乎已滿足了我們平常所有的需求。
流的操作有三種:創(chuàng)建流、修改流元素(中間操作)、消費流(終端操作)
流的創(chuàng)建:
流的創(chuàng)建方式有以下這幾種方式:
-
Stream.of():需要傳入一組元素參數(shù)。
public class StreamOf {
public static void main(String[] args) {
Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!");
.forEach(System.out::println);
}
}
// 中間需要傳入一組元素參數(shù)
// 代碼中傳入的是一組字符串元素,forEach() 傳入一個方法引用。
-
stream(): 對于集合類,可以直接調(diào)用集合類中的stream()方法:
List<String> list = Arrays.asList("It's a wonderful day for pie!".split(" "))
list.stream()
.forEach(System.out::print);
-
Stream.generate(): 需要傳入一個 Supplier<T> ,Suppier 是一個通用的函數(shù)式接口,所以可以配合Lambda 使用。
// Stream.generate() 中間傳入的參數(shù) Generator 類實現(xiàn)Supplier 接口,
//所以實現(xiàn)的 get(),Stream.generate(new Generator()) 會自動調(diào)用 get() 產(chǎn)生所需要的流元素。
public class Generator implements Supplier<String> {
Random rand = new Random(47);
char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
public String get() {
return "" + letters[rand.nextInt(letters.length)];
}
public static void main(String[] args) {
String word = Stream.generate(new Generator())
.limit(30)
.collect(Collectors.joining());
System.out.println(word);
}
}
搭配 lambda 表達式使用:傳入與 supplier 接口中 get() 方法簽名相同的方法引用
Stream.generate(() -> "duplicate")
.limit(3)
.forEach(System.out::println);
// 輸出
// “duplicate”
// “duplicate”
// “duplicate”
-
Stream.iterate(): 該函數(shù)有兩個參數(shù),以種子(第一個參數(shù))開頭,并將其傳給方法(第二個參數(shù),需要傳入一個方法引用)。方法的結(jié)果將添加到流,并存儲作為第一個參數(shù)用于下次調(diào)用 iterate()。
基本類型流的生成
-
InStream.range(): 生成整型序列的流,range()需要傳入兩個參數(shù),指定一個序列范圍。 -
LongStream:生成 Long 序列的流 -
DoubleStream:生成 double 序列的流
public class Ranges {
public static void main(String[] args) {
System.out.println(range(10, 20).sum());
}
}
// sum() :用于將流中的元素相加求和
數(shù)組流生成
-
Arrays.stream(數(shù)組):利用Arrays類中含有一個名為stream()的靜態(tài)方法將數(shù)組轉(zhuǎn)換成為流
Arrays.stream(new double[] { 3.14159, 2.718, 1.618 })
.forEach(n -> System.out.format("%f ", n));
同時也可以
Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6)
此時Arrays.stream() 的調(diào)用有兩個額外的參數(shù)。第一個參數(shù)告訴 stream() 從數(shù)組的哪個位置開始選擇元素,第二個參數(shù)用于告知在哪里停止。
-
splitAsStream():Java 8 在 java.util.regex.Pattern 中增加的一個新的方法。這個方法可以根據(jù)傳入的公式將字符序列轉(zhuǎn)化為流。但是有一個限制,輸入只能是 CharSequence,因此不能將流作為 splitAsStream() 的參數(shù)。
中間操作
中間操作,用于從一個流中獲取對象,并進行處理,再將對象作為另一個流從后端輸出,以連接到其他操作。
查看流中元素
peek(): 該操作的目的是幫助調(diào)試。它允許你無修改地查看流中的元素。因為 peek() 符合無返回值的 Consumer 函數(shù)式接口,所以我們只能觀察,無法使用不同的元素來替換流中的對象
排序
sorted() 操作排序,使用默認比較器實現(xiàn),另一種形式是傳入一個比較器。另一種形式的實現(xiàn):傳入一個 Comparator 參數(shù)。
移除元素
distinct(): 可用于消除流中的重復(fù)元素。相比創(chuàng)建一個 Set 集合,該方法的工作量要少得多。
filter(Predicate):過濾操作會保留與傳遞進去的過濾器函數(shù)計算結(jié)果為 true 元素。
isPrime() 作為過濾器函數(shù),用于檢測質(zhì)數(shù)。
應(yīng)用函數(shù)到元素
map()會獲取流中的所有元素,并且對流中元素應(yīng)用函數(shù)操作從而產(chǎn)生新的元素,并將其傳遞到后續(xù)的流中。mapToInt(ToIntFunction):操作同上,但結(jié)果是IntStream。mapToLong(ToLongFunction):操作同上,但結(jié)果是LongStream。mapToDouble(ToDoubleFunction):操作同上,但結(jié)果是DoubleStream。
使用 map 也可以產(chǎn)生和接收類型不同的類型
map(類::new) 傳入一個類的構(gòu)造器。可以把接收類型轉(zhuǎn)換為其他類。例如 int 轉(zhuǎn)成一個自定義的類型。
在 flatMap 中組合流
map() 函數(shù)的功能是產(chǎn)生流,而如果我們傳入的元素是流,則會產(chǎn)生元素流的流。
此時可以利用 flatMap() :該函數(shù)功能與 map 一樣。但不同是該函數(shù)會把流扁平化為元素,最終產(chǎn)生的是元素。
public class FlatMap {
public static void main(String[] args) {
Stream.of(1, 2, 3)
.flatMap(i -> Stream.of("Gonzo", "Fozzie", "Beaker"))
.forEach(System.out::println);
}
}
其他操作:
ints(): 方法產(chǎn)生一個流并且 ints() 方法有多種方式的重載 — 兩個參數(shù)限定了數(shù)值產(chǎn)生的邊界.
相應(yīng)其他數(shù)據(jù)類型的操作:longs()limit(): 操作獲取指定數(shù)量的元素
limit(8):或者前 8 個元素。forEach()方法遍歷輸出,它根據(jù)傳遞給它的函數(shù)對每個流對象執(zhí)行操作。
通常使用該方法傳入System.out::println來遍歷輸出流中的每個元素。boxed(): 流操作將會自動地把基本類型包裝成為對應(yīng)的裝箱類型, 對應(yīng)解箱 unboxed()skip()操作 : 它根據(jù)參數(shù)丟棄指定數(shù)量的流元素
Optional 對象
一些標準流返回 Optional 對象,此對象可以作為流的持有者,即使查看的元素不存在也能友好的提示我們。
流中的一些操作方法
findFirst() 返回一個包含第一個元素的 Optional 對象,如果流為空則返回 Optional.empty
findAny() 返回包含任意元素的 Optional 對象,如果流為空則返回 Optional.empty
max() 和 min() 返回一個包含最大值或者最小值的 Optional 對象,如果流為空則返回 Optional.empty
class OptionalsFromEmptyStreams {
public static void main(String[] args) {
System.out.println(Stream.<String>empty()
.findFirst());
}
}
// 輸出:Optional.empty
當流為空時,返回 Optional.empty 對象。
空流可以通過 Stream.<String>empty() 創(chuàng)建,也可以運用 Stream.empty(),使用此方法時,需要賦值給一個指定的類型的變量,不然 java 編輯器無法進行類型推導(dǎo),即
Stream<String> s = Stream.empty()
當接收到 Optional 對象時,應(yīng)首先調(diào)用 isPresent()檢查其中是否包含元素。如果存在,可使用 get() 獲取。
class OptionalBasics {
static void test(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst());
}
}
便利函數(shù)
通常對于流返回的 Optional對象,我們需要進行解包,才能獲取到里面的元素。
流中提供了一些便利函數(shù),用于解包 Optional 對象,簡化了 “對所包含的對象的檢查和執(zhí)行操作”:
-
ifPresent(Consumer):當值存在時調(diào)用Consumer,否則什么也不做。 -
orElse(otherObject):如果值存在則直接返回,否則生成otherObject。 -
orElseGet(Supplier):如果值存在則直接返回,否則使用Supplier函數(shù)生成一個可替代對象。 -
orElseThrow(Supplier):如果值存在直接返回,否則使用Supplier函數(shù)生成一個異常。
創(chuàng)建 Optional
當需要在代碼中添加 Optional對象時,可以利用 Optional對象的靜態(tài)方法來創(chuàng)建:
-
empty():生成一個空 Optional。 -
of(value):將一個非空值包裝到 Optional 里。 -
ofNullable(value):針對一個可能為空的值,為空時自動生成Optional.empty,否則將值包裝在Optional中。
Optional 對象的操作
當流管道生成了 Optional 對象,可以使用下列一些方法對對象進行操作。
filter(Predicate):將Predicate應(yīng)用于Optional中的內(nèi)容并返回結(jié)果。當Optional不滿足Predicate時返回空。如果Optional為空,則直接返回。map(Function):如果Optional不為空,應(yīng)用Function于Optional中的內(nèi)容,并返回結(jié)果。否則直接返回Optional.empty。flatMap(Function):同map(),但是提供的映射函數(shù)將結(jié)果包裝在Optional對象中,因此flatMap()不會在最后進行任何包裝。
一般來說,流的 filter() 會在 Predicate 返回 false 時移除流元素。而Optional.filter()在失敗時不會刪除 Optional,而是將其保留下來,并轉(zhuǎn)化為空。
終端操作
數(shù)組
-
toArray():將流轉(zhuǎn)換成適當類型的數(shù)組。 -
toArray(generator):在特殊情況下,生成自定義類型的數(shù)組。
循環(huán)
-
forEach(Consumer)常見如System.out::println作為 Consumer 函數(shù)。 -
forEachOrdered(Consumer): 保證forEach按照原始流順序操作
集合
-
collect(Collector):使用Collector收集流元素到結(jié)果集合中。 -
collect(Supplier, BiConsumer, BiConsumer):同上,第一個參數(shù)Supplier創(chuàng)建了一個新結(jié)果集合,第二個參數(shù)BiConsumer將下一個元素包含到結(jié)果中,第三個參數(shù)BiConsumer用于將兩個值組合起來。
Collectors 還有其他更加復(fù)雜的實現(xiàn),可以通過查看文檔了解。
例如:實現(xiàn)將流元素收集到 TreeSet 中的集合。可以利用將集合的的構(gòu)造函數(shù)引用傳遞給 Collectors.toCollection(),從而構(gòu)建任何類型的集合
public class TreeSetOfWords {
public static void
main(String[] args) throws Exception {
Set<String> words2 =
Files.lines(Paths.get("TreeSetOfWords.java"))
.flatMap(s -> Arrays.stream(s.split("\\W+")))
.filter(s -> !s.matches("\\d+")) // No numbers
.map(String::trim)
.filter(s -> s.length() > 2)
.limit(100)
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(words2);
}
}
Files.lines() 打開 Path 并將其轉(zhuǎn)換成為行流。
組合
-
reduce(BinaryOperator):使用BinaryOperator來組合所有流中的元素。因為流可能為空,其返回值為Optional。 -
reduce(identity, BinaryOperator):功能同上,但是使用 identity 作為其組合的初始值。因此如果流為空,identity 就是結(jié)果。 -
reduce(identity, BiFunction, BinaryOperator):更復(fù)雜的使用形式(暫不介紹),這里把它包含在內(nèi),因為它可以提高效率。通常,我們可以顯式地組合 map() 和 reduce() 來更簡單的表達它。
匹配
-
allMatch(Predicate):如果流的每個元素根據(jù)提供的 Predicate 都返回 true 時,結(jié)果返回為 true。在第一個 false 時,則停止執(zhí)行計算。 -
anyMatch(Predicate):如果流中的任意一個元素根據(jù)提供的 Predicate 返回 true 時,結(jié)果返回為 true。在第一個 false 是停止執(zhí)行計算。 -
noneMatch(Predicate):如果流的每個元素根據(jù)提供的 Predicate 都返回 false 時,結(jié)果返回為 true。在第一個 true 時停止執(zhí)行計算。
查找
-
findFirst():返回第一個流元素的 Optional,如果流為空返回 Optional.empty。 -
findAny():返回含有任意流元素的 Optional,如果流為空返回 Optional.empty。
信息
-
count():流中的元素個數(shù)。
max(Comparator):根據(jù)所傳入的 Comparator 所決定的“最大”元素。 -
min(Comparator):根據(jù)所傳入的 Comparator 所決定的“最小”元素。
數(shù)字流信息
-
average():求取流元素平均值。 -
max() 和 min():數(shù)值流操作無需 Comparator。 -
sum():對所有流元素進行求和。 -
summaryStatistics():生成可能有用的數(shù)據(jù)。目前并不太清楚這個方法存在的必要性,因為我們其實可以用更直接的方法獲得需要的數(shù)據(jù)。
collect() 收集操作,它根據(jù)參數(shù)來組合所有流中的元素。
當傳入的參數(shù)是Collectors.joining(),你將會得到一個 String 類型的結(jié)果,每個元素都根據(jù) joining() 的參數(shù)來進行分割。其他不同的 Collectors 的參數(shù)則會產(chǎn)生不同的結(jié)果