Stream是什麼?
Java從8開始,不但引入了Lambda表示式,還引入了一個全新的流式API:Stream API。它位於java.util.stream
包中。
Stream 使用一種類似用 SQL 語句從資料庫查詢資料的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到前面處理的結果。
Stream和IO包下的InputStream和OutputStream一樣嗎?
劃重點:這個Stream
不同於java.io
的InputStream
和OutputStream
,它代表的是任意Java物件的序列。兩者對比如下:
java.io | java.util.stream | |
---|---|---|
儲存 | 順序讀寫的byte 或char |
順序輸出的任意Java物件例項 |
用途 | 序列化至檔案或網路 | 記憶體計算/業務邏輯 |
這時候大家可能又有疑問了,那麼既然是順序輸出的任意Java物件例項,那麼和List集合不就相同了嗎?
再次劃重點:這個Stream
和List
也不一樣,List
儲存的每個元素都是已經儲存在記憶體中的某個Java物件,而Stream
輸出的元素可能並沒有預先儲存在記憶體中,而是實時計算出來的。
換句話說,List
的用途是操作一組已存在的Java物件,而Stream
實現的是惰性計算,兩者對比如下:
java.util.List | java.util.stream | |
---|---|---|
元素 | 已分配並儲存在記憶體 | 可能未分配,實時計算 |
用途 | 操作一組已存在的Java物件 | 惰性計算 |
關於惰性計算在下面的章節中可以看到。
Stream特點
Stream介面還包含幾個基本型別的子介面如IntStream, LongStream 和 DoubleStream。
特點:
- 不儲存資料:流是基於資料來源的物件,它本身不儲存資料元素,而是通過管道將資料來源的元素傳遞給操作。
- 函數語言程式設計:流的操作不會修改資料來源,例如
filter
不會將資料來源中的資料刪除。 - 延遲操作:流的很多操作如filter,map等中間操作是延遲執行的,只有到終點操作才會將操作順序執行。
- 純消費:流的元素只能訪問一次,類似Iterator,操作沒有回頭路,如果你想從頭重新訪問流的元素,對不起,你得重新生成一個新的流。
Stream的建立
Stream的建立有多種方式,下面給大家一一列舉出來
1、Stream.of()
這種方式一般不常用的,但是測試的時候比較方便
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4");
//forEach()方法相當於內部迴圈呼叫
//引數的寫法是Lambda表示式
stream.forEach(s -> System.out.println(s));
}
}
關於Lambda表示式,在我的這篇部落格中有詳細介紹,感興趣的朋友可以去看一下
2、基於陣列或者Collection
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream1 = Arrays.stream(new String[] { "1", "2", "3" });
Stream<String> stream2 = List.of("X", "Y", "Z").stream();
stream1.forEach(System.out::println);
stream2.forEach(System.out::println);
}
}
這兩種建立Stream的方式是我們工作中經常會用到的方式,藉助Stream(轉化、聚合等方法)可以幫助我們更方便的去輸出我們想要的結果
3、其他方式
-
使用流的靜態方法,比如
Stream.of(Object[])
,IntStream.range(int, int)
或者Stream.iterate(Object, UnaryOperator)
,如Stream.iterate(0, n -> n * 2)
,或者generate(Supplier<T> s)
如Stream.generate(Math::random)
。 -
BufferedReader.lines()
從檔案中獲得行的流。 -
Files
類的操作路徑的方法,如list
、find
、walk
等。 -
隨機數流
Random.ints()
。 -
其它一些類提供了建立流的方法,如
BitSet.stream()
,Pattern.splitAsStream(java.lang.CharSequence)
, 和JarFile.stream()
。 -
更底層的使用
StreamSupport
,它提供了將Spliterator
轉換成流的方法。
Stream常用API(中間操作)
還記得我們在前面介紹Stream的時候提到了一個惰性計算。惰性計算的特點是:一個Stream
轉換為另一個Stream
時,實際上只儲存了轉換規則,並沒有任何計算髮生。中間操作會返回一個新的流,它不會修改原始的資料來源,而且是由在終點操作開始的時候才真正開始執行。
1、distinct
distinct
保證輸出的流中包含唯一的元素,它是通過Object.equals(Object)
來檢查是否包含相同的元素。
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c", "b","c","d").distinct();
stream.forEach(System.out::println);
}
}
//輸出結果
a
b
c
d
2、filter
從字面看是過濾的意思,過濾掉不滿足條件的資料
import java.util.stream.IntStream;
public class StreamTest {
public static void main(String[] args) {
IntStream stream = IntStream.range(1, 10).filter(i -> i % 2 == 0); //filter中的引數是過濾條件
stream.forEach(System.out::println);
}
}
//輸出結果
2
4
6
8
3、map
map方法可以將流中的值對映成另外的值,比如將字串全部轉化成小寫
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Hello WORLD HELLO Life").map(s -> s.toLowerCase());
stream.forEach(System.out::println);
}
}
//輸出結果
hello world hello life
從輸出結果我們可以看到,字串全部轉化成小寫字元了
4、limit
limit方法指定流的元素數列,類似於Mysql中的limit方法
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3", "4", "5", "6").limit(3); //取三條
stream.forEach(System.out::println);
}
}
// 輸出結果
1
2
3
5、peek
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Hello WORLD HELLO Life").peek(s -> {
String peek = s.toLowerCase();
System.out.println(peek);
});
stream.forEach(System.out::println);
}
}
//輸出結果
hello world hello life
Hello WORLD HELLO Life
有沒有發現出一些東西?
我們將這段程式碼用上面的map方法實現一下
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Hello WORLD HELLO Life").map(s -> {
String peek = s.toLowerCase();
System.out.println(peek);
return peek;
});
stream.forEach(System.out::println);
}
}
// 輸出結果
hello world hello life
hello world hello life
peek方法的定義如下:
Stream<T> peek(Consumer<? super T> action);
peek方法接收一個Consumer的入參。瞭解λ表示式的應該明白 Consumer的實現類 應該只有一個方法,該方法返回型別為void。
而map方法的入參為 Function。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
我們發現Function 比 Consumer 多了一個 return。這也就是peek 與 map的區別了。
6、skip
skip
返回丟棄了前n個元素的流,如果流中的元素小於或者等於n,則返回空的流。
7、sorted
sorted()
將流中的元素按照自然排序方式進行排序
import java.util.stream.Stream;
public class QueryTest {
public static void main(String[] args) {
//自定義排序
customSort();
//自然排序
naturalSort();
}
public static void customSort() {
Stream stream = Stream.of("hello", "I", "love", "you").sorted((str1, str2) -> {
// 自定義排序規則
if (str1 == null) {
return -1;
}
if (str2 == null) {
return 1;
}
return str1.length() - str2.length();
});
System.out.println("-----------自定義排序-----------");
stream.forEach(System.out::println);
}
public static void naturalSort() {
Stream<String> stream = Stream.of("hello", "I", "love", "you").sorted();
System.out.println("-----------自然排序------------");
stream.forEach(System.out::println);
}
}
// 輸出結果
-----------自定義排序-----------
I
you
love
hello
-----------自然排序------------
I
hello
love
you
如果我們直接呼叫sorted()方法,那麼將按照自然排序,如果我們希望元素按照我們想要的結果來排序,需要自定義排序方法,sorted(Comparator<? super T> comparator)
可以指定排序的方式。如果元素沒有實現Comparable
,則終點操作執行時會丟擲java.lang.ClassCastException
異常。
Stream常用API(終點操作)
1、max、min、count
max:獲取最大值
min:獲取最小值
count:返回流的數量
2、reduce
reduce操作可以實現從一組元素中生成一個值,max()
、min()
、count()
等都是reduce操作,將他們單獨設為函式只是因為常用。reduce()
的方法定義有三種重寫形式:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
3、count
獲取Stream數量
package com.mybatisplus;
import java.util.stream.Stream;
public class QueryTest {
public static void main(String[] args) {
long count = Stream.of("a", "b", "A", "a", "c", "a").count();
System.out.println(count);
}
}
//輸出結果 6
4、Match
anyMatch表示,判斷的條件裡,任意一個元素成功,返回true
allMatch表示,判斷條件裡的元素,所有的都是,返回true
noneMatch跟allMatch相反,判斷條件裡的元素,所有的都不是,返回true
package com.mybatisplus;
import java.util.stream.Stream;
public class QueryTest {
public static void main(String[] args) {
boolean b1 = Stream.of("a", "b", "A", "a", "c", "a").anyMatch(str -> str.equals("a"));
boolean b2 = Stream.of("a", "b", "A", "a", "c", "a").allMatch(str -> str.equals("a"));
boolean b3 = Stream.of("a", "b", "A", "a", "c", "a").noneMatch(str -> str.equals("a"));
System.out.println("b1 = " + b1);
System.out.println("b2 = " + b2);
System.out.println("b3 = " + b3);
}
}
// 輸出結果
b1 = true
b2 = false
b3 = false