流操作

无功發表於2024-07-18

生成流的方式主要有五種

1、透過集合生成,應用中最常用的一種

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> stream = integerList.stream();

2、透過陣列生成

int[] intArr = {1, 2, 3, 4, 5, 6};
IntStream stream = Arrays.stream(intArr);

透過Arrays.stream方法生成流,並且該方法生成的流是數值流【即IntStream】而不是 Stream。補充一點使用數值流可以避免計算過程中拆箱裝箱,提高效能。

Stream API提供了mapToInt、mapToDouble、mapToLong三種方式將物件流【即Stream 】轉換成對應的數值流,同時提供了boxed方法將數值流轉換為物件流.

3、透過值生成

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

透過Stream的of方法生成流,透過Stream的empty方法可以生成一個空流.

4、透過檔案生成

Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());

透過Files.line方法得到一個流,並且得到的每個流是給定檔案中的一行.

5、透過函式生成

iterator

Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);

iterate方法接受兩個引數,第一個為初始化值,第二個為進行的函式操作,因為iterator生成的流為無限流,透過limit方法對流進行了截斷,只生成5個偶數。

generator

Stream<Double> stream = Stream.generate(Math::random).limit(5);

generate方法接受一個引數,方法引數型別為Supplier ,由它為流提供值。generate生成的流也是無限流,因此透過limit對流進行了截斷。

3、流的操作型別

流的操作型別主要分為兩種

3.1、中間操作

一個流可以後面跟隨零個或多箇中間操作。其目的主要是開啟流,做出某種程度的資料對映/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的,僅僅呼叫到這類方法,並沒有真正開始流的遍歷,真正的遍歷需等到終端操作時,常見的中間操作有下面即將介紹的 filter、map 等。

3.2、終端操作

一個流有且只能有一個終端操作,當這個操作執行後,流就被關閉了,無法再被操作,因此一個流只能被遍歷一次,若想在遍歷需要透過源資料在生成流。終端操作的執行,才會真正開始流的遍歷。如下面即將介紹的 count、collect 等。

4、流的使用

4.1 中間操作

filter 篩選

透過使用filter方法進行條件篩選,filter的方法引數為一個條件(過濾保留函式返回值為 true 的元素)。

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6);


Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

結果為:4,5,6

distinct 去重

透過distinct方法快速去除重複的元素。

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);


Stream<Integer> stream = integerList.stream().distinct();

結果為:1,2,3,4,5

limit 返回指定流個數

透過limit方法指定返回流的個數,limit的引數值必須 >=0,否則將會丟擲異常。

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);


Stream<Integer> stream = integerList.stream().limit(3);

結果為: 1,1,2

skip 跳過流中的元素

透過skip方法跳過流中的元素。

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);


Stream<Integer> stream = integerList.stream().skip(2);

結果為: 2,3,4,5

skip的引數值必須>=0,否則將會丟擲異常。

map 流對映

所謂流對映就是將接受的元素對映成另外一個元素。

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


List<Integer> collect = stringList.stream()
        .map(String::length)
        .collect(Collectors.toList());

結果為:[6, 7, 2, 6]

透過map方法可以完成對映,該例子完成中 String -> Integer 的對映。

flatMap 流轉換

將一個流中的每個值都轉換為另一個流.

List<String> wordList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


List<String> strList = wordList.stream()
        .map(w -> w.split(" "))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());

結果為:[Java, 8, Lambdas, In, Action]

map(w -> w.split(" ")) 的返回值為 Stream<String[]>,想獲取 Stream,可以透過flatMap方法完成 Stream ->Stream 的轉換。

allMatch 匹配所有元素

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


if (integerList.stream().allMatch(i -> i > 3)) {
    System.out.println("所有元素值都大於3");
} else {
    System.out.println("並非所有元素值都大於3");
}

anyMatch匹配其中一個

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


if (integerList.stream().anyMatch(i -> i > 3)) {
    System.out.println("存在值大於3的元素");
} else {
    System.out.println("不存在值大於3的元素");
}

等同於

for (Integer i : integerList) {
    if (i > 3) {
        System.out.println("存在大於3的值");
        break;
    }
}

noneMatch全部不匹配

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);




if (integerList.stream().noneMatch(i -> i > 3)) {
    System.out.println("值都小於3的元素");
} else {
    System.out.println("值不都小於3的元素");

4.2 終端操作

count 統計流中元素個數

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


Long result = integerList.stream().count();

結果為:5

findFirst 查詢第一個

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();


System.out.println(result.orElse(-1))

結果為:4

findAny 隨機查詢一個

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();


System.out.println(result.orElse(-1))

結果為:4

透過findAny方法查詢到其中一個大於三的元素並列印,因為內部進行最佳化的原因,當找到第一個滿足大於三的元素時就結束,該方法結果和findFirst方法結果一樣。提供findAny方法是為了更好的利用並行流,findFirst方法在並行上限制更多【本篇文章將不介紹並行流】。

reduce 將流中的元素組合

用於求和:

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


int sum = integerList.stream()
        .reduce(0, Integer::sum);

結果為:15

等同於

int sum = 0;
for (int i : integerList) {
    sum += i;
}

reduce接受兩個引數,一個初始值這裡是0,一個 BinaryOperatoraccumulator

來將兩個元素結合起來產生一個新值,另外reduce方法還有一個沒有初始化值的過載方法。

用於獲取最大最小值

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


Optional<Integer> min = stringList.stream()
        .map(String::length)
        .reduce(Integer::min);


Optional<Integer> max = stringList.stream()
        .map(String::length)
        .reduce(Integer::max)

結果為:Optional[2] 和 Optional[7]

min/max 獲取最小最大值

方法引數為 Comparator<?superT>comparator

寫法1:

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


Optional<Integer> min = stringList.stream()
        .map(String::length)
        .min(Integer::compareTo);


Optional<Integer> max = stringList.stream()
        .map(String::length)
        .max(Integer::compareTo)

結果為:Optional[2] 和 Optional[7]

寫法2:

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


OptionalInt
        min = stringList.stream()
        .mapToInt(String::length)
        .min();


OptionalInt
        max = stringList.stream()
        .mapToInt(String::length)
        .max()

方法3:使用reduce獲取最大最小值

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


Optional<Integer> min = stringList.stream()
        .map(String::length)
        .reduce(Integer::min);


Optional<Integer> max = stringList.stream()
        .map(String::length)
        .reduce(Integer::max)

sum / summingxxx / reduce 求和

求和的實現方式有很多種如下:

方式1:sum(推薦)

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


int sum = stringList.stream()
        .mapToInt(String::length)
        .sum();

方式2:summingInt

如果資料型別為double、long,則透過summingDouble、summingLong方法進行求和。

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


int sum = stringList.stream()
        .collect(summingInt(String::length));

方式3:reduce

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


int sum = stringList.stream()
        .map(String::length)
        .reduce(0, Integer::sum);

在上面求和、求最大值、最小值的時候,對於相同操作有不同的方法可以選擇執行。可以選擇collect、reduce、min/max/sum方法,推薦使用min、max、sum方法。因為它最簡潔易讀,同時透過mapToInt將物件流轉換為數值流,避免了裝箱和拆箱操作

averagingxxx 求平均值

方式1:averagingxxx

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


double average = stringList.stream()
        .collect(averagingInt(String::length));

summarizingxxx 同時求總和、平均值、最大值、最小值

如果資料型別為double、long,則透過summarizingDouble、summarizingLong方法

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


IntSummaryStatistics intSummaryStatistics = stringList.stream()
        .collect(summarizingInt(String::length));


double average = intSummaryStatistics.getAverage(); // 獲取平均值
int min = intSummaryStatistics.getMin();            // 獲取最小值
int max = intSummaryStatistics.getMax();            // 獲取最大值
long sum = intSummaryStatistics.getSum();           // 獲取總

foreach 遍歷

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


stringList.stream().forEach(System.out::println);

collect 返回集合

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


List<Integer> intList = stringList.stream()
        .map(String::length)
        .collect(toList());


Set<Integer> intSet = stringList.stream()
        .map(String::length)
        .collect(toSet())

等價於

List<Integer> intList = new ArrayList<>();
Set<Integer> intSet = new HashSet<>();
for (String item : stringList) {
    intList.add(item.length());
    intSet.add(item.length());
}

透過遍歷和返回集合的使用發現流只是把原來的外部迭代放到了內部進行,這也是流的主要特點之一。內部迭代可以減少好多程式碼量。

joining 拼接流中的元素

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


String result = stringList.stream()
        .map(String::toLowerCase)
        .collect(Collectors.joining("-"));

結果為:java 8-lambdas-in-action

groupingBy 分組

在collect方法中傳入groupingBy進行分組,其中groupingBy的方法引數為分類函式。

List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");


Map<Integer, List<String>> collect = stringList.stream().collect(groupingBy(String::length));

結果為:{2=[In], 6=[Java 8, Action], 7=[Lambdas]}

還可以透過巢狀使用groupingBy進行多級分類

List<String> stringList = Arrays.asList("Java 12", "Lambdas", "In", "Action");


Map<Integer, Map<Integer, List<String>>> collect = stringList.stream()
        .collect(groupingBy(
                String::length,
                groupingBy(String::hashCode)
        ));

結果為:

{2={2373=[In]}, 6={1955883606=[Action]}, 7={1611513196=[Lambdas], -155279169=[Java 12]}}

List<String> stringList = Arrays.asList("Java 12", "Lambdas", "In", "Action");


Map<Integer, Map<String, List<String>>> collect = stringList.stream()
        .collect(groupingBy(
                String::length,
                groupingBy(item -> {
                    if (item.length() <= 2) {
                        return "level1";
                    } else if (item.length() <= 6) {
                        return "level2";
                    } else {
                        return "level3";
                    }
                })
        ));

結果為:{2={level1=[In]}, 6={level2=[Action]}, 7={level3=[Java 12, Lambdas]}}

partitioningBy 分割槽

分割槽是特殊的分組,它分類依據是true和false,所以返回的結果最多可以分為兩組。

List<String> stringList = Arrays.asList("Java 12", "Lambdas", "In", "Action");


Map<Boolean, List<String>> collect = stringList.stream().collect(partitioningBy(String::isEmpty));

結果為:{false=[Java 12, Lambdas, In, Action], true=[]}

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);


Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));

結果為:{false=[3, 4, 5], true=[1, 2]}

相關文章