Java8 Stream完全使用指南

MartinDai發表於2024-06-10

什麼是Stream

Stream是Java 1.8版本開始提供的一個介面,主要提供對資料集合使用流的方式進行操作,流中的元素不可變且只會被消費一次,所有方法都設計成支援鏈式呼叫。使用Stream API可以極大生產力,寫出高效率、乾淨、簡潔的程式碼。

如何獲得Stream例項

Stream提供了靜態構建方法,可以基於不同的引數建立返回Stream例項
使用Collection的子類例項呼叫stream()或者parallelStream()方法也可以得到Stream例項,兩個方法的區別在於後續執行Stream其他方法的時候是單執行緒還是多執行緒

Stream<String> stringStream = Stream.of("1", "2", "3");
//無限長的偶數流
Stream<Integer> evenNumStream = Stream.iterate(0, n -> n + 2);

List<String> strList = new ArrayList<>();
strList.add("1");
strList.add("2");
strList.add("3");
Stream<String> strStream = strList.stream();
Stream<String> strParallelStream = strList.parallelStream();

filter

filter方法用於根據指定的條件做過濾,返回符合條件的流

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//獲得只包含正數的流,positiveNumStream -> (1,2,3)
Stream<Integer> positiveNumStream = numStream.filter(num -> num > 0);

map

map方法用於將流中的每個元素執行指定的轉換邏輯,返回其他型別元素的流

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//轉換成字串流
Stream<String> strStream = numStream.map(String::valueOf);

mapToInt mapToLong mapToDouble

這三個方法是對map方法的封裝,返回的是官方為各個型別單獨定義的Stream,該Stream還提供了適合各自型別的其他操作方法

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
IntStream intStream = stringStream.mapToInt(Integer::parseInt);
LongStream longStream = stringStream.mapToLong(Long::parseLong);
DoubleStream doubleStream = stringStream.mapToDouble(Double::parseDouble);

flatMap

flatMap方法用於將流中的每個元素轉換成其他型別元素的流,比如,當前有一個訂單(Order)列表,每個訂單又包含多個商品(itemList),如果要得到所有訂單的所有商品彙總,就可以使用該方法,如下:

Stream<Item> allItemStream = orderList.stream().flatMap(order -> order.itemList.stream());

flatMapToInt flatMapToLong flatMapToDouble

這三個方法是對flatMap方法的封裝,返回的是官方為各個型別單獨定義的Stream,使用方法同上

distinct

distinct方法用於對流中的元素去重,判斷元素是否重複使用的是equals方法

Stream<Integer> numStream = Stream.of(-2, -1, 0, 0, 1, 2, 2, 3);
//不重複的數字流,uniqueNumStream -> (-2, -1, 0, 1, 2, 3)
Stream<Integer> uniqueNumStream = numStream.distinct();

sorted

sorted有一個無參和一個有參的方法,用於對流中的元素進行排序。無參方法要求流中的元素必須實現Comparable介面,不然會報java.lang.ClassCastException異常

Stream<Integer> unorderedStream = Stream.of(5, 6, 32, 7, 27, 4);
//按從小到大排序完成的流,orderedStream -> (4, 5, 6, 7, 27, 32)
Stream<Integer> orderedStream = unorderedStream.sorted();

有參方法sorted(Comparator<? super T> comparator)不需要元素實現Comparable介面,透過指定的元素比較器對流內的元素進行排序

Stream<String> unorderedStream = Stream.of("1234", "123", "12", "12345", "123456", "1");
//按字串長度從小到大排序完成的流,orderedStream -> ("1", "12", "123", "1234", "12345", "123456")
Stream<String> orderedStream = unorderedStream.sorted(Comparator.comparingInt(String::length));

peek

peek方法可以不調整元素順序和數量的情況下消費每一個元素,然後產生新的流,按文件上的說明,主要是用於對流執行的中間過程做debug的時候使用,因為Stream使用的時候一般都是鏈式呼叫的,所以可能會執行多次流操作,如果想看每個元素在多次流操作中間的流轉情況,就可以使用這個方法實現

Stream.of("one", "two", "three", "four")
     .filter(e -> e.length() > 3)
     .peek(e -> System.out.println("Filtered value: " + e))
     .map(String::toUpperCase)
     .peek(e -> System.out.println("Mapped value: " + e))
     .collect(Collectors.toList());
     
輸出:
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

limit(long maxSize)

limit方法會對流進行順序擷取,從第1個元素開始,保留最多maxSize個元素

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//擷取前3個元素,subStringStream -> ("-2", "-1", "0")
Stream<String> subStringStream = stringStream.limit(3);

skip(long n)

skip方法用於跳過前n個元素,如果流中的元素數量不足n,則返回一個空的流

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//跳過前3個元素,subStringStream -> ("1", "2", "3")
Stream<String> subStringStream = stringStream.skip(3);

forEach

forEach方法的作用跟普通的for迴圈類似,不過這個可以支援多執行緒遍歷,但是不保證遍歷的順序

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//單執行緒遍歷輸出元素
stringStream.forEach(System.out::println);
//多執行緒遍歷輸出元素
stringStream.parallel().forEach(System.out::println);

forEachOrdered

forEachOrdered方法可以保證順序遍歷,比如這個流是從外部傳進來的,然後在這之前呼叫過parallel方法開啟了多執行緒執行,就可以使用這個方法保證單執行緒順序遍歷

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//順序遍歷輸出元素
stringStream.forEachOrdered(System.out::println);
//多執行緒遍歷輸出元素,下面這行跟上面的執行結果是一樣的
//stringStream.parallel().forEachOrdered(System.out::println);

toArray

toArray有一個無參和一個有參的方法,無參方法用於把流中的元素轉換成Object陣列

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
Object[] objArray = stringStream.toArray();

有參方法toArray(IntFunction<A[]> generator)支援把流中的元素轉換成指定型別的元素陣列

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
String[] strArray = stringStream.toArray(String[]::new);

reduce

reduce有三個過載方法,作用是對流內元素做累進操作

第一個reduce(BinaryOperator<T> accumulator)

accumulator 為累進操作的具體計算

單執行緒等下如下程式碼

boolean foundAny = false;
T result = null;
for (T element : this stream) {
  if (!foundAny) {
      foundAny = true;
      result = element;
  }
  else
      result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//查詢最小值
Optional<Integer> min = numStream.reduce(BinaryOperator.minBy(Integer::compareTo));
//輸出 -2
System.out.println(min.get());

//過濾出大於5的元素流
numStream = Stream.of(-2, -1, 0, 1, 2, 3).filter(num -> num > 5);
//查詢最小值
min = numStream.reduce(BinaryOperator.minBy(Integer::compareTo));
//輸出 Optional.empty
System.out.println(min);

第二個reduce(T identity, BinaryOperator<T> accumulator)

identity 為累進操作的初始值
accumulator 同上

單執行緒等價如下程式碼

T result = identity;
for (T element : this stream)
  result = accumulator.apply(result, element)
return result;
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//累加計算所有元素的和,sum=3
int sum = numStream.reduce(0, Integer::sum);

第三個reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

identityaccumulator同上
combiner用於多執行緒執行的情況下合併最終結果

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
int sum = numStream.parallel().reduce(0, (a, b) -> {
    System.out.println("accumulator執行:" + a + " + " + b);
    return a + b;
}, (a, b) -> {
    System.out.println("combiner執行:" + a + " + " + b);
    return a + b;
});
System.out.println("最終結果:"+sum);

輸出:
accumulator執行:0 + -1
accumulator執行:0 + 1
accumulator執行:0 + 0
accumulator執行:0 + 2
accumulator執行:0 + -2
accumulator執行:0 + 3
combiner執行:2 + 3
combiner執行:-1 + 0
combiner執行:1 + 5
combiner執行:-2 + -1
combiner執行:-3 + 6
最終結果:3

collect

collect有兩個過載方法,主要作用是把流中的元素作為集合轉換成其他Collection的子類,其內部實現類似於前面的累進操作

第一個collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)

supplier 需要返回開始執行時的預設結果
accumulator 用於累進計算用
combiner 用於多執行緒合併結果

單執行緒執行等價於如下程式碼

R result = supplier.get();
for (T element : this stream)
  accumulator.accept(result, element);
return result;

第二個collect(Collector<? super T, A, R> collector)

collector其實是對上面的方法引數的一個封裝,內部執行邏輯是一樣的,只不過JDK提供了一些預設的Collector實現

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
List<Integer> numList = numStream.collect(Collectors.toList());
Set<Integer> numSet = numStream.collect(Collectors.toSet());

min

min方法用於計算流內元素的最小值

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
Optional<Integer> min = numStream.min(Integer::compareTo);

max

min方法用於計算流內元素的最大值

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
Optional<Integer> max = numStream.max(Integer::compareTo);

count

count方法用於統計流內元素的總個數

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//count=6
long count = numStream.count();

anyMatch

anyMatch方法用於匹配校驗流內元素是否有符合指定條件的元素

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//判斷是否包含正數,hasPositiveNum=true
boolean hasPositiveNum = numStream.anyMatch(num -> num > 0);

allMatch

allMatch方法用於匹配校驗流內元素是否所有元素都符合指定條件

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//判斷是否全部是正數,allNumPositive=false
boolean allNumPositive = numStream.allMatch(num -> num > 0);

noneMatch

noneMatch方法用於匹配校驗流內元素是否都不符合指定條件

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//判斷是否沒有小於0的元素,noNegativeNum=false
boolean noNegativeNum = numStream.noneMatch(num -> num < 0);

findFirst

findFirst方法用於獲取第一個元素,如果流是空的,則返回Optional.empty

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//獲取第一個元素,firstNum=-2
Optional<Integer> firstNum = numStream.findFirst();

findAny

findAny方法用於獲取流中的任意一個元素,如果流是空的,則返回Optional.empty,因為可能會使用多執行緒,所以不保證每次返回的是同一個元素

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
Optional<Integer> anyNum = numStream.findAny();

相關文章