- Java 8 中的 Stream 俗稱為流,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念
- Stream 用于對集合對象進行各種非常便利、高效的聚合操作,或者大批量資料操作
- Stream API 借助于 Lambda 表達式,極大的提高程式設計效率和程式可讀性
- 同時它提供串行和并行兩種模式進行彙聚操作,并發模式能夠充分利用多核處理器的優勢
- 通過下面的例子我們可以初步體會到使用 Stream 處理集合的便利性
初探Stream
- 有如下一個 List 集合,現要從中篩選出以
開頭的元素,然後轉換為大寫,最後輸出結果
J
- Java 8之前我們是這樣做的:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
List<String> filterList = new ArrayList<>();
for (String str : list) {
if (str.startsWith("J")) {
filterList.add(str.toUpperCase());
}
}
for (String str : filterList) {
System.out.println(str);
}
}
}
- 為了篩選集合我們進行了兩次外部疊代,并且還建立了一個用來臨時存放篩選元素的集合對象
- 借助Java 8中的Stream我們可以極大的簡化這個處理過程:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
list.stream()
.filter(s -> s.startsWith("J"))
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
是不是很友善?上面的例子中,集合使用
stream
方法建立了一個流,然後使用
filter
和
map
方法來處理這個集合,它們統稱為 中間操作。中間操作都會傳回另一個流,以便于将各種對集合的操作連接配接起來形成一條流水線。最後我們使用了
forEach
方法疊代篩選結果,這種位于流的末端,對流進行處理并且生成結果的方法稱為 終端操作。
總而言之,流的使用一般包括三件事情:
- 一個資料源(如集合)來執行一個查詢
- 一個中間操作鍊,形成一條流的流水線
- 一個終端操作,執行流水線,并能生成結果
下表列出了流中常見的中間操作和終端操作:
操作 | 類型 | 傳回類型 | 使用的類型 / 函數式接口 | 函數描述符 |
filter | 中間 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中間 | Stream<T> | ||
skip | 中間 | Stream<T> | long | |
limit | 中間 | Stream<T> | long | |
map | 中間 | Stream<R> | Function<T, R> | T -> R |
flatMap | 中間 | Stream<R> | Function<T, Stream<R>> | T -> Stream<R> |
sorted | 中間 | Stream<T> | Comparator<T> | (T, T) -> int |
anyMatch | 終端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 終端 | boolean | Predicate<T> | T -> boolean |
allMatch | 終端 | boolean | Predicate<T> | T -> boolean |
findAny | 終端 | Optional<T> | ||
findFirst | 終端 | Optional<T> | ||
forEach | 終端 | void | Consumer<T> | T -> void |
collect | 終端 | R | Collector<T, A, R> | |
reduce | 終端 | Optional<T> | BinaryOperator<T> | (T, T) -> T |
count | 終端 | long |
- 下面詳細介紹這些操作的使用
- 除了特殊說明,使用下面這個集合作為示範:
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
中間操作
filter
- Streams 接口支援
方法,該方法接收一個
filter
,函數描述符為
Predicate<T>
,用于對集合進行篩選,傳回所有滿足的元素:
T -> boolean
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.filter(s -> s.contains("#"))
.forEach(System.out::println);
}
}
- 結果輸出
C#
distinct
-
方法用于
distinct
流中重複的元素,類似于 SQL 中的 distinct 操作
排除
- 比如篩選中集合中所有的偶數,并排除重複的結果:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
}
}
- 結果輸出
2 4
skip
-
方法用于跳過流中的
skip(n)
,如果集合元素小于n,則傳回空流
前n個元素
- 比如篩選出以
開頭的元素,并排除第一個:
J
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.filter(s -> s.startsWith("J"))
.skip(1)
.forEach(System.out::println);
}
}
- 結果輸出
JavaScript
limit
-
方法傳回一個長度不超過
limit(n)
的流,比如下面的例子将輸出
n
:
Java JavaScript python
- 例如你輸入的 3,傳回的就是3,不會超過3
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.limit(3)
.forEach(System.out::println);
}
}
map
-
方法接收一個函數作為參數
map
- 這個函數會被應用到每個元素上,并将其映射成一個新的元素
- 如:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.map(String::length)
.forEach(System.out::println);
}
}
- 結果輸出
4 10 6 3 2 6 5 3 4
-
還支援将流特化為指定的原始類型的流,如通過
map
,
mapToInt
和
mapToDouble
方法,可以将流轉換為
mapToLong
,
IntStream
和
DoubleStream
LongStream
- 特化後的流支援
,
sum
和
min
方法來對流中的元素進行計算
max
- 比如:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
IntStream intStream = numbers.stream().mapToInt(a -> a);
System.out.println(intStream.sum());
}
}
- 也可以通過下面的方法,将
轉換為
IntStream
:
Stream
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
IntStream intStream = numbers.stream().mapToInt(a -> a);
Stream<Integer> s = intStream.boxed();
}
}
flatMap
-
用于将多個流合并成一個流,俗稱流的扁平化
flatMap
- 這麼說有點抽象,舉個例子,比如現在需要将 list 中的各個元素拆分為一個個字母,并過濾掉重複的結果,你可能會這樣做:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.map(s -> s.split(""))
.distinct()
.forEach(System.out::println);
}
}
- 輸出結果如下:
[Ljava.lang.String;@58372a00
[Ljava.lang.String;@4dd8dc3
[Ljava.lang.String;@6d03e736
[Ljava.lang.String;@568db2f2
[Ljava.lang.String;@378bf509
[Ljava.lang.String;@5fd0d5ae
[Ljava.lang.String;@2d98a335
[Ljava.lang.String;@16b98e56
[Ljava.lang.String;@7ef20235
- 這明顯不符合我們的預期
- 實際上在
操作後,傳回了一個
map(s -> s.split(""))
類型的流,是以輸出結果為每個數組對象的句柄,而我們真正想要的結果是
Stream<String[]>
!
Stream<String>
- 在 Stream 中,可以使用
方法來将數組轉換為流,改造上面的方法:
Arrays.stream()
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.map(s -> s.split(""))
.map(Arrays::stream)
.distinct()
.forEach(System.out::println);
}
}
- 輸出結果如下:
java.util.stream.ReferencePipeline$Head@568db2f2
java.util.stream.ReferencePipeline$Head@378bf509
java.util.stream.ReferencePipeline$Head@5fd0d5ae
java.util.stream.ReferencePipeline$Head@2d98a335
java.util.stream.ReferencePipeline$Head@16b98e56
java.util.stream.ReferencePipeline$Head@7ef20235
java.util.stream.ReferencePipeline$Head@27d6c5e0
java.util.stream.ReferencePipeline$Head@4f3f5b24
java.util.stream.ReferencePipeline$Head@15aeb7ab
- 因為上面的流經過
處理後,将每個數組變成了一個新的流,傳回結果為流的數組
map(Arrays::stream)
,是以輸出是各個流的句柄
Stream<String>[]
- 我們還需将這些新的流連接配接成一個流,使用
來改寫上面的例子:
flatMap
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.map(s -> s.split(""))
.flatMap(Arrays::stream)
.distinct()
.forEach(s -> System.out.print(s + " "));
}
}
- 輸出結果如下:
J a v S c r i p t y h o n P H C # G l g w f + R u b
- 和
類似,
map
方法也有相應的原始類型特化方法,如
flatMap
等
flatMapToInt
終端操作
anyMatch
-
方法用于判斷流中是否有符合判斷條件的元素,傳回值為
anyMatch
boolean類型
- 比如判斷 list 中是否含有
元素:
SQL
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
System.out.println(list.stream()
.anyMatch(s -> "SQL".equals(s)));
}
}
allMatch
-
方法用于判斷流中是否所有元素都滿足給定的判斷條件,傳回值為
allMatch
boolean類型
- 比如判斷 list 中是否所有元素長度都不大于10:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
System.out.println(list.stream()
.allMatch(s -> s.length() <= 10));
}
}
noneMatch
-
方法用于判斷流中是否所有元素都不滿足給定的判斷條件,傳回值為
noneMatch
boolean類型
- 比如判斷 list 中不存在長度大于10的元素:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
System.out.println(list.stream()
.noneMatch(s -> s.length() > 10));
}
}
findAny
-
方法用于傳回流中的任意元素的 Optional 類型
findAny
- 例如篩選出 list 中任意一個以
開頭的元素,如果存在,則輸出它:
J
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.filter(s -> s.startsWith("J"))
.findAny()
.ifPresent(System.out::println);
}
}
findFirst
-
方法用于傳回流中的第一個元素的 Optional 類型
findFirst
- 例如篩選出 list 中長度大于 5 的元素,如果存在,則輸出第一個:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream()
.filter(s -> s.length() > 5)
.findFirst()
.ifPresent(System.out::println);
}
}
reduce
-
函數從字面上來看就是壓縮,縮減的意思,它可以用于數字類型的流的求和,求最大值和最小值。如對numbers中的元素求和:
reduce
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
System.out.println(numbers.stream().reduce(0, Integer::sum));
}
}
-
函數也可以不指定初始值,但這時候将傳回一個
reduce
對象,比如求最大值和最小值:
Optional
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.reduce(Integer::max)
.ifPresent(System.out::println);
numbers.stream()
.reduce(Integer::min)
.ifPresent(System.out::println);
}
}
forEach
-
用于疊代流中的每個元素,最為常見的就是疊代輸出,如:
forEach
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
list.stream().forEach(System.out::println);
}
}
count
-
方法用于統計流中的元素的個數,比如:
count
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
System.out.println(list.stream().count());
}
}
collect
-
方法用于收集流中的元素,并放到不同類型的結果中,比如
collect
、
List
或者
Set
Map
- 舉個例子:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");
List<String> filterList = list.stream().filter(s -> s.startsWith("J")).collect(Collectors.toList());
System.out.println(filterList);
}
}
- 如果需要以
來替代
Set
,隻需要使用
List
就好了
Collectors.toSet()
流的建構
- 除了使用集合對象的
方法建構流之外,我們可以手動建構一些流
stream
數值範圍建構
-
和
IntStream
對象支援
LongStream
和
range
方法來建構數值流
rangeClosed
- 這兩個方法都是第一個參數接受起始值,第二個參數接受結束值
- 但
是不包含結束值的,而
range
則包含結束值
rangeClosed
- 比如對 1 到 100 的整數求和:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
System.out.println(IntStream.rangeClosed(1, 100).sum());
}
}
由值建構
- 靜态方法
可以顯式值建立一個流
Stream.of
- 它可以接受任意數量的參數
- 例如,以下代碼直接使用
建立了一個字元串流:
Stream.of
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Stream<String> s = Stream.of("Java", "JavaScript", "C++", "Ruby");
}
}
- 也可以使用
建構一個空流:
Stream.empty()
Stream<Object> emptyStream = Stream.empty();
由數組建構
- 靜态方法
可以通過數組建立一個流
Arrays.stream
- 它接受一個數組作為參數
- 例如:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(arr);
}
}
由檔案生成流
-
中的很多靜态方法都會傳回一個流
java.nio.file.Files
- 例如
方法會傳回一個由指定檔案中的各行構成的字元串流
Files.lines
- 比如統計一個檔案中共有多少個字:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
long wordCout = 0L;
try (Stream<String> lines = Files.lines(Paths.get("file.txt"), Charset.defaultCharset())) {
wordCout = lines.map(l -> l.split(""))
.flatMap(Arrays::stream)
.count();
} catch (Exception ignore) {
}
System.out.println(wordCout);
}
}
由函數構造
- Stream API 提供了兩個靜态方法來從函數生成流:
和
Stream.iterate
Stream.generate
- 這兩個操作可以建立所謂的無限流
- 比如下面的例子建構了 10 個偶數:
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Stream.iterate(0, n -> n + 2)
.limit(10).forEach(System.out::println);
}
}
-
方法接受一個初始值(在這裡是0)還有一個依次應用在每個産生的新值上的 Lambda(UnaryOperator類型)
iterate
- 這裡,我們使用 Lambda
,傳回的是前一個元素加上 2
n -> n + 2
- 是以,
方法生成了一個所有正偶數的流:流的第一個元素是初始值0
iterate
- 然後加上 2 來生成新的值 2,再加上 2 來得到新的值 4,以此類推
- 與
方法類似,
iterate
方法也可讓你按需生成一個無限流
generate
- 但
不是依次對每個新生成的值應用函數,比如下面的例子生成了 5 個 0 到 1 之間的随機雙精度數:
generate
/**
* @author BNTang
**/
public class Demo {
public static void main(String[] args) {
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
}
}
- 輸出結果如下:
0.4477477019693912
0.8866972547736678
0.6893219838296453
0.3768607796229386
0.9647978867306028