切換語言為:簡體

Java stream實戰:30個案例精通集合篩選、歸約、分組與聚合操作

  • 爱糖宝
  • 2024-09-28
  • 2063
  • 0
  • 0
  • 一、Stream流的特點和使用流程

    • 1. 特點

    • 2. 使用流程

  • 二、Stream流的魅力

  • 三、stream流的建立

    • 1. 透過集合建立

    • 2. 透過陣列建立

    • 3. 透過Stream的靜態方法

    • 4. 透過隨機數生成

    • 5. 透過檔案I/O

    • 6. 無限流

    • 7. 透過範圍建立

  • 四、Stream流的應用

    • 1. 中間操作

    • 2. 終端操作

    • 3. 收集操作

    • 4 其他操作:sequential(順序流)/parallel(並行流)

Java stream實戰:30個案例精通集合篩選、歸約、分組與聚合操作

一、Stream流的特點和使用流程

Stream API允許開發者以宣告性方式處理資料集合。可以簡化複雜的資料操作,並且支援並行處理以提高效能。

1. 特點

  • 宣告性: Stream API允許你描述你想要做什麼,而不是詳細說明怎麼做。
  • 鏈式操作: 可將多個操作連結在一起,形成一個流水線,每個操作都會生成一個新的流供下一個操作使用。
  • 函數語言程式設計: Stream API鼓勵使用無副作用的函式和 lambda 表示式。
  • 並行處理: Stream API支援並行流,可以充分利用多核處理器。
  • 延遲執行: Stream 操作是惰性的,只有在終端操作(如 collect、forEach)被呼叫時,整個流水線纔會執行。
  • 短路操作: 某些終端操作(如 anyMatch、allMatch、noneMatch、findFirst)在找到結果後會立即停止處理。

2. 使用流程

  • 建立流: 從資料來源(如集合、陣列、檔案等)建立一個流。
  List<String> list = Arrays.asList("a", "b", "c");     
  Stream<String> stream = list.stream();
  • 中間操作: 對流進行一系列轉換操作,如 filter(過濾)、map(對映)、sorted(排序)等。這些操作會返回一個新的流,不會立即執行。
Stream<String> filteredStream = stream.filter(s -> s.startsWith("a"));
  • 終端操作: 執行一個終端操作來結束流的處理併產生結果。終端操作會觸發整個流水線的執行,並且不會返回一個新的流。
List<String> result = filteredStream.collect(Collectors.toList());
  • 處理結果: 使用終端操作返回的結果進行後續處理。
 result.forEach(System.out::println);

流只能被使用一次。一旦終端操作被觸發,流就會被關閉,無法再次使用。

二、Stream流的魅力

以下是一個:分組、排序然後提取每組中最小和最大值的案例,我們來看一下使用stream和不使用stream的程式碼實現。

List<Integer> numbers = Arrays.asList(1, 5, 3, 8, 10, 2, 6, 7, 4, 9);  
  
// 分組、排序並提取最小和最大值  
 Map<Boolean, List<Integer>> result = numbers.stream()  
      .collect(Collectors.groupingBy(n -> n % 2 == 0, 
      // 分組:奇數和偶數  
       Collectors.collectingAndThen(  
                Collectors.toList(), 
                // 收集到列表  
                list -> {  
                     // 對列表進行排序  
                    Collections.sort(list);  
                    // 提取並返回最小和最大值
                    return Lists.newArrayList(list.get(0),list.get(list.size() - 1));  
               }  
)));

如果我們不使用stream,程式碼可能是這樣的

import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.Collections;  
import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
  
public class GroupAndSort {  
    public static void main(String[] args) {  
        List<Integer> numbers = Arrays.asList(1, 5, 3, 8, 10, 2, 6, 7, 4, 9);  
  
        Map<Boolean, List<Integer>> result = new HashMap<>();  
        result.put(true, new ArrayList<>()); 
        // 偶數分組  
        result.put(false, new ArrayList<>()); 
        // 奇數分組  
  
        for (Integer number : numbers) {  
            if (number % 2 == 0) {  
                result.get(true).add(number);  
            } else {  
                result.get(false).add(number);  
            }  
        }  
  
        // 對分組後的列表進行排序  
        Collections.sort(result.get(true));  
        Collections.sort(result.get(false));  
  
        // 提取並設定最小和最大值(注意這裏需要使用get(list.size() - 1)來獲取最大值)  
        List<Integer> evenResult = result.get(true);  
        if (evenResult.size() > 1) {  
            result.put(true, Arrays.asList(evenResult.get(0), evenResult.get(evenResult.size() - 1)));  
        } else if (evenResult.size() == 1) {  
            result.put(true, Arrays.asList(evenResult.get(0), evenResult.get(0))); 
            // 如果只有一個元素,則最小值和最大值相同  
        } else {  
            result.put(true, Collections.emptyList()); 
            // 如果沒有元素,則為空列表  
        }  
  
        List<Integer> oddResult = result.get(false);  
        if (oddResult.size() > 1) {  
            result.put(false, Arrays.asList(oddResult.get(0), oddResult.get(oddResult.size() - 1)));  
        } else if (oddResult.size() == 1) {  
            result.put(false, Arrays.asList(oddResult.get(0), oddResult.get(0))); 
            // 如果只有一個元素,則最小值和最大值相同  
        } else {  
            result.put(false, Collections.emptyList()); 
            // 如果沒有元素,則為空列表  
        }   
    }  
}

三、stream流的建立

1. 透過集合建立

呼叫集合物件的stream()方法來獲取一個流

List<String> list = Arrays.asList("a", "b", "c");  
Stream<String> streamFromList = list.stream();

2. 透過陣列建立

使用Arrays.stream()方法從陣列建立流,這適用於任何型別的陣列。

int[] array = {1, 2, 3, 4, 5};  
IntStream intStream = Arrays.stream(array);  
 
// 或者對於物件陣列  
String[] strArray = {"a", "b", "c"};  
Stream<String> stringStream = Arrays.stream(strArray);

基本型別的陣列,Arrays.stream()會返回特定型別的流,如IntStream、LongStream或DoubleStream。如果需要將這些流轉換為通用Stream ,你可以使用boxed()方法。

3. 透過Stream的靜態方法

Stream類提供了幾個靜態方法來建立流

Stream<String> stream = Stream.of("a", "b", "c");

4. 透過隨機數生成

Random類可以用於生成隨機數,並且你可以使用ints()、longs()或doubles()方法建立一個無限流。但通常會結合limit()方法來限制流的長度。

Random random = new Random();  
IntStream randomIntStream = random.ints(10, 0, 100);
  // 生成10個0到100之間的隨機數

5. 透過檔案I/O

在處理檔案時,可以使用Files類中的方法,如lines(),從檔案中讀取行並建立一個流。

Path path = Paths.get("path/to/file.txt");  
Stream<String> linesStream = Files.lines(path);

6. 無限流

Stream API 提供了能夠生成無限序列的流

  • Stream.iterate(T seed, UnaryOperator f): 建立一個無限順序的流,透過反覆應用函式f來生成元素。
  • Stream.generate(Supplier s): 建立一個無限無序的流,其中每個元素由提供的Supplier生成。
// 使用iterate生成一個無限遞增的整數流  
Stream<Integer> infiniteIntegerStream = Stream.iterate(0, i -> i + 1);  
 
// 使用generate生成一個無限的隨機數流  
Stream<Double> infiniteRandomStream = Stream.generate(Math::random);

7. 透過範圍建立

IntStream.range(int startInclusive, int endExclusive): 
LongStream.range(int startInclusive, int endExclusive):

建立一個包含從startInclusive(包含)到endExclusive(不包含)之間的整數的流。

// 建立一個從0(包含)到10(不包含)的整數流  
IntStream intStream = IntStream.range(0, 10);

無限流應該結合limit()或其他短路操作。

四、Stream流的應用

1. 中間操作

1.1 Filter(過濾)/map(轉換)/mapToInt/mapToDouble/mapToLong

filter方法用於過濾流中的元素,而map方法用於對流中的每個元素執行某種操作,並返回一個新的流。

import java.util.Arrays;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class EmployeeFilterMapDemo {  
    public static void main(String[] args) {  
        // 建立一個員工列表:包含姓名和薪水兩個屬性  
        List<Employee> employees = Arrays.asList(  
                new Employee("Alice", 5500.0),  
                new Employee("Bob", 6000.0),  
                new Employee("Charlie", 4500.0),  
                new Employee("Diana", 5700.0)  
        );  
  
        // 使用Stream API的filter方法過濾出工資超過5000的員工,  
        // 然後使用map方法將每個員工對映成他們的名字,並收集到一個新的列表中  
        List<String> namesOfHighSalaryEmployees = employees.stream()  
                .filter(e -> e.getSalary() > 5000.0)  
                .map(Employee::getName)  
                .collect(Collectors.toList());  
  
        // 列印過濾並對映後的員工名字列表  
        System.out.println("Names of employees with salary > 5000: " + namesOfHighSalaryEmployees);  
    }  
}

此外streamAPI中還有指定型別的map方法:

  • mapToInt(ToIntFunction<? super T> mapper): 將流中的元素轉換成int型別。
  • mapToLong(ToLongFunction<? super T> mapper): 將流中的元素轉換成long型別。
  • mapToDouble(ToDoubleFunction<? super T> mapper): 將流中的元素轉換成double型別。

1.2 flatMap(轉換)

flatMap方法在Java Stream API中用於將流中的每個元素轉換成一個新的流,然後將這些新生成的流合併成一個單一的流。用於處理流中的集合或陣列元素,以將它們“展平”成一個單一的元素流。

一個包含字串列表的列表使用flatMap將其轉換成一個包含所有字串的單一流:

import java.util.Arrays;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class FlatMapExample {  
    public static void main(String[] args) {  
        // 建立一個包含列表的列表  
        List<List<String>> listOfLists = Arrays.asList(  
            Arrays.asList("A", "B", "C"),  
            Arrays.asList("D", "E"),  
            Arrays.asList("F", "G", "H", "I")  
        );  
  
        // 使用flatMap將內部列表展平成一個單一列表  
        List<String> flatList = listOfLists.stream()  
            .flatMap(List::stream) // 使用List的stream方法將每個列表轉換成流,然後合併  
            .collect(Collectors.toList());  
  
        // 列印結果  
        System.out.println(flatList);  
    }  
}
/// 輸出結果將是:
[A, B, C, D, E, F, G, H, I]

1.3 Distinct(去重)

distinct方法用於去除流中的重複元素。基於元素的 equals 和 hashCode 方法來確定哪些元素是重。

import java.util.Arrays;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class DistinctExample {  
    public static void main(String[] args) {  
        // 建立一個包含重複元素的列表  
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 5, 1);  
  
        // 使用distinct方法去除重複元素  
        List<Integer> distinctNumbers = numbers.stream()  
            .distinct()  
            .collect(Collectors.toList());  
  
        // 列印結果  
        System.out.println(distinctNumbers);  
    }  
}
// 輸出結果將是:

[1, 2, 3, 4, 5]

1.4 Limit(限制)/Skip(跳過)/Peek(展示)

limit用於限制流中的元素數量,skip用於跳過流中的前N個元素,而peek則允許對流中的每個元素執行某種操作(如列印、修改等)而不改變流本身。peek通常用於除錯或檢視流中的元素。

import java.util.Arrays;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class LimitSkipPeekExample {  
    public static void main(String[] args) {  
        // 建立一個整數列表  
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
  
        // 使用peek列印流中的元素,然後使用limit和skip獲取特定元素  
        List<Integer> result = numbers.stream()  
                .peek(System.out::println) // 列印每個元素  
                .skip(2)                   // 跳過前兩個元素  
                .limit(3)                  // 獲取接下來的三個元素  
                .collect(Collectors.toList()); // 收集結果  
  
        // 列印最終結果  
        System.out.println("Result after skip and limit: " + result);  
    }  
}

輸出結果將是:

1  
2  
3  
4  
5  
Result after skip and limit: [3, 4, 5]

peek方法雖然執行了操作,但它不會改變流中的元素或流的結構。

1.5 Sorted(排序)

排序可以透過sorted()方法實現,該方法有兩種形式:

  • 無參的sorted(),它使用元素的自然順序進行排序(要求元素實現Comparable介面);
  • 以及接受Comparator引數的sorted(Comparator<? super T> comparator),它允許自定義排序規則。
import java.util.Arrays;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class SortingExample {  
    public static void main(String[] args) {  
        // 建立一個字串列表  
        List<String> words = Arrays.asList("banana", "apple", "cherry", "date", "elderberry");  
  
        // 使用sorted()方法按自然順序排序  
        List<String> sortedWords = words.stream()  
                .sorted()  
                .collect(Collectors.toList());  
  
        // 列印排序後的結果  
        System.out.println("Sorted words in natural order: " + sortedWords);  
  
        // 建立一個整數列表  
        List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);  
  
        // 使用sorted()方法和自定義Comparator進行排序  
        List<Integer> sortedNumbers = numbers.stream()  
                .sorted((a, b) -> b - a) // 降序排序  
                .collect(Collectors.toList());  
  
        // 列印排序後的結果  
        System.out.println("Sorted numbers in descending order: " + sortedNumbers);  
    }  
}

輸出結果:

Sorted words in natural order: [apple, banana, cherry, date, elderberry]  
Sorted numbers in descending order: [9, 7, 5, 2, 1]

1.6 concat(兩個流連線成一個流)

concat(Stream<? extends T> a, Stream<? extends T> b): 靜態方法,用於將兩個流連線成一個流。

  // 建立兩個整數列表  
List<Integer> list1 = Arrays.asList(1, 2, 3);  
List<Integer> list2 = Arrays.asList(4, 5, 6);  
  
// 將兩個列表轉換為流,並使用 concat 方法連線它們  
Stream<Integer> concatenatedStream = Stream.concat(list1.stream(), list2.stream());  
// 使用連線後的流進行一些操作,比如列印所有元素  
concatenatedStream.forEach(System.out::println);

2. 終端操作

2.1  forEach/findFirst/findAny

Stream API中,forEach、findFirst和findAny都是終端操作。forEach用於迭代流中的每個元素並執行一個操作,findFirst用於獲取流中的第一個元素,而findAny則用於獲取流中的任意元素(並行流特別有用,因為它可能更快)。

import java.util.Arrays;  
import java.util.List;  
import java.util.Optional;  
  
public class StreamMethodsExample {  
    public static void main(String[] args) {  
        // 建立一個整數列表  
        List<Integer> numbers = Arrays.asList(1, 2, 3);  
  
        // 使用forEach列印每個元素  
        numbers.stream()  
                .forEach(System.out::println);  
  
        // 使用findFirst獲取第一個元素  
        Optional<Integer> firstNumber = numbers.stream()  
                .findFirst();  
  
        System.out.println("First number: " + firstNumber.orElse(null));  
  
        // 使用findAny獲取任意元素  
        Optional<Integer> anyNumber = numbers.stream()  
                .findAny();  
  
        System.out.println("Any number: " + anyNumber.orElse(null));  
    }  
}

輸出結果將是流中的每個數字

1  
2  
3  
First number: 1  
Any number: 1

findFirst和findAny返回的是一個Optional物件,這是因為流可能是空的。

2.3 count/sum/max/min

count、sum、max和min都是終端操作,用於對流中的元素進行計數、求和、找最大值和最小值。

// 建立一個整數列表  
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
  
// 使用count計算元素數量  
long count = numbers.stream()  
        .count();  
  
System.out.println("Count of elements: " + count);  
  
// 使用sum計算元素總和  
int sum = numbers.stream()  
        .mapToInt(Integer::intValue) // 轉換為IntStream以使用sum  
        .sum();  
  
System.out.println("Sum of elements: " + sum);  
  
// 使用max獲取最大值  
OptionalDouble max = numbers.stream()  
        .mapToDouble(Integer::doubleValue) // 轉換為DoubleStream以使用max  
        .max();  
  
System.out.println("Max value: " + max.orElse(Double.NaN));  
  
// 使用min獲取最小值  
OptionalDouble min = numbers.stream()  
        .mapToDouble(Integer::doubleValue) // 轉換為DoubleStream以使用min  
        .min();  
  
System.out.println("Min value: " + min.orElse(Double.NaN));

輸出結果:

Count of elements: 10  
Sum of elements: 55  
Max value: 10.0  
Min value: 1.0

2.4 anyMatch/allMatch/noneMatch

anyMatch、allMatch和noneMatch是終端操作,用於檢查流中的元素是否滿足給定的謂詞(條件)。anyMatch檢查是否有任何元素滿足條件,allMatch檢查是否所有元素都滿足條件,而noneMatch檢查是否沒有任何元素滿足條件。

// 建立一個整數列表  
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
  
// 使用anyMatch檢查是否有任何偶數  
boolean hasEven = numbers.stream()  
        .anyMatch(n -> n % 2 == 0);  
  
System.out.println("Has any even number? " + hasEven);  
  
// 使用allMatch檢查是否所有數字都小於11  
boolean allLessThan11 = numbers.stream()  
        .allMatch(n -> n < 11);  
  
System.out.println("Are all numbers less than 11? " + allLessThan11);  
  
// 使用noneMatch檢查是否沒有任何數字等於0  
boolean noZeros = numbers.stream()  
        .noneMatch(n -> n == 0);  
  
System.out.println("Are there no zeros? " + noZeros);

輸出結果:

Has any even number? true  
Are all numbers less than 11? true  
Are there no zeros? true

2.5 歸約reduce

reduce方法是一個終端操作,用於將流中的所有元素組合成一個單一的結果。它通常用於執行某種累積操作,比如計算元素的總和、乘積或連線字串等。

  • reduce(T identity, BinaryOperator accumulator) 此方法接受一個初始值和一個累積函式,用於歸約流中的元素。
  • reduce(BinaryOperator accumulator) 此方法不接受初始值,而是使用流中的第一個元素作為初始值,然後應用累積函式。

計算一個員工列表中所有員工的總薪水,同時找出薪水最高的員工。

public class Employee {  
    private String name;  
    private double salary;  
    ... 
}

使用reduce方法來計算總薪水和找出薪水最高的員工:

// 建立一個員工列表  
List<Employee> employees = Arrays.asList(  
        new Employee("Alice", 5000.0),  
        new Employee("Bob", 6000.0),  
        new Employee("Charlie", 5500.0),  
        new Employee("David", 6500.0)  
);  
  
// 使用初始值的 reduce 方法來計算所有員工的總薪水 
doubletotalSalary = employees.stream()  
                .reduce(0, (acc, employee) -> acc + employee.getSalary(), Integer::sum); 
System.out.println("Total salary of all employees: " + totalSalary);  
// 不使用初始值的 reduce 方法來連線所有員工的名字  
Optional<String> combinedNames = employees.stream()  
                .map(Employee::getName)  
                .reduce((name1, name2) -> name1 + ", " + name2);    
// 使用reduce找出薪水最高的員工  
Optional<Employee> highestPaidEmployee = employees.stream()  
        .reduce((emp1, emp2) -> emp1.getSalary() > emp2.getSalary() ? emp1 : emp2); 
  
System.out.println("Highest paid employee: " + highestPaidEmployee.orElse(null));  
  
// 或者使用max方法找出薪水最高的員工(更簡潔)  
Optional<Employee> highestPaidEmployeeWithMax = employees.stream()  
        .max(Comparator.comparingDouble(Employee::getSalary));  
  
System.out.println("Highest paid employee (using max): " + highestPaidEmployeeWithMax.orElse(null));

使用max方法和Comparator.comparingDouble來更簡潔地找出薪水最高的員工。它更清晰地表達了我們的意圖,並且程式碼更簡潔。

輸出結果:

Total salary of all employees: 23000.0  
Highest paid employee: Employee{name='David', salary=6500.0}  
Highest paid employee (using max): Employee{name='David', salary=6500.0}

3. 收集操作

3.1 collect收集(三個引數)

collect方法在Java Stream API中通常用於收集流中的元素到某種集合或其他數據結構中。

  • collect(Supplier supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner):

過載版本的collect方法提供了更高的靈活性,允許你自定義收集過程。 這個collect方法接受三個引數:

  • Supplier supplier:一個供應器,用於建立新的結果容器。
  • BiConsumer<R, ? super T> accumulator:一個累加器,用於將流中的元素新增到結果容器中。
  • BiConsumer<R, R> combiner:一個組合器,用於合併兩個結果容器(通常用於並行流)。

自定義一個收集過程,將流中的字串連線成一個單獨的字串:

 // 建立一個字串流  
Stream<String> stringStream = Stream.of("Hello", " ", "World", "!", " ", "Welcome", " ", "to", " ", "Java");  
  
// 使用自定義的 collect 方法來連線字串  
String concatenated = stringStream.collect(  
            // 供應器:建立一個 StringBuilder  
            StringBuilder::new,  
            // 累加器:將每個字串新增到 StringBuilder  
            StringBuilder::append,  
            // 組合器:將兩個 StringBuilder 合併(這裏其實不需要,因為我們是順序處理的,但爲了示例完整性還是提供了)  
            (left, right) -> left.append(right)  
        ).toString();  
  
// 輸出結果  
System.out.println(concatenated);

3.1 toList/toMap/toSet/toArray()

toList(), toMap(), 和 toSet() 是非常有用的終端操作,它們可以將流中的元素收集到相應的集合中

List<Employee> employees = Arrays.asList(  
                new Employee("Alice", 5000.0),  
                new Employee("Bob", 6000.0),  
                new Employee("Charlie", 5500.0),  
                new Employee("David", 6500.0)  
        );  
  
        // toList  
        List<String> employeeNames = employees.stream()  
                .map(Employee::getName)  
                .collect(Collectors.toList());  
        System.out.println("Employee Names (toList): " + employeeNames);  
  
        // toSet  
        Set<Double> uniqueSalaries = employees.stream()  
                .map(Employee::getSalary)  
                .collect(Collectors.toSet());  
        System.out.println("Unique Salaries (toSet): " + uniqueSalaries);  
  
        // toMap 
        Map<String, Double> employeeSalaries = employees.stream()  
                .collect(Collectors.toMap(  
                        Employee::getName,  
                        Employee::getSalary  
                ));  
        System.out.println("Employee Salaries (toMap): " + employeeSalaries);  

       // 使用 Stream API 將員工列表轉換為 Employee[] 陣列  
        Person[] employeeArray = employees.stream()  
            .toArray(Employee[]::new); 
    }

3.3 summing/averaging/summarizing

Collectors 類提供了幾個用於資料統計的收集器,如 averagingDouble、summarizingDouble 和 summingDouble。這些收集器通常與流的 collect 方法一起使用,用於對數值流(如員工薪水)進行統計。 Collectors提供了一系列用於資料統計的靜態方法: 計數:count 平均值:averagingInt、averagingLong、averagingDouble 最值:maxBy、minBy 求和:summingInt、summingLong、summingDouble 統計以上所有:summarizingInt、summarizingLong、summarizingDouble

List<Employee> employees = Arrays.asList(  
                new Employee("Alice", 5000.0),  
                new Employee("Bob", 6000.0),  
                new Employee("Charlie", 5500.0),  
                new Employee("David", 6500.0)  
        );  
  
        // 使用 averagingDouble 計算平均薪水  
        double averageSalary = employees.stream()  
                .collect(Collectors.averagingDouble(Employee::getSalary));  
        System.out.println("Average Salary (averagingDouble): " + averageSalary);  
  
        // 使用 summarizingDouble 獲取薪水的統計資訊  
        DoubleSummaryStatistics salaryStats = employees.stream()  
                .collect(Collectors.summarizingDouble(Employee::getSalary));  
        System.out.println("Salary Statistics (summarizingDouble): " + salaryStats);  
  
        // 使用 summingDouble 計算薪水總和  
        double sumSalary = employees.stream()  
                .collect(Collectors.summingDouble(Employee::getSalary));  
        System.out.println("Sum of Salaries (summingDouble): " + sumSalary);  
    }

輸出結果:

Average Salary (averagingDouble): 5750.0  
Salary Statistics (summarizingDouble): DoubleSummaryStatistics{count=4, sum=23000.000000, min=5000.000000, average=5750.000000, max=6500.000000}  
Sum of Salaries (summingDouble): 23000.0

3.4 summaryStatistics()

對於數值流(如 IntStream, LongStream, DoubleStream),summaryStatistics返回描述該流統計資訊的物件,如最小值、最大值、平均值等。

// 建立一個包含一些雙精度浮點數的陣列  
double[] numbers = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};  
  
// 使用 DoubleStream 的 of 方法建立一個流,然後呼叫 summaryStatistics 方法  
 DoubleSummaryStatistics stats = Arrays.stream(numbers).summaryStatistics();  
  
 // 輸出統計資訊  
System.out.println("最小值: " + stats.getMin());  
System.out.println("最大值: " + stats.getMax());  
System.out.println("平均值: " + stats.getAverage());  
System.out.println("元素數量: " + stats.getCount());  
System.out.println("元素總和: " + stats.getSum());

3.6 接合joining

Collectors.joining可以將流中的元素連線成一個字串。這對將列表、集合或其他流數據結構轉換為單個字串表示形式特別有用。

List<Employee> employees = Arrays.asList(  
                new Employee("Alice", 5000.0),  
                new Employee("Bob", 6000.0),  
                new Employee("Charlie", 5500.0),  
                new Employee("David", 6500.0)  
        );  
  
        // 使用 joining 將員工名字連線成一個字串,逗號分隔  
        String namesJoined = employees.stream()  
                .map(Employee::getName) 
// 提取員工的名字  
                .collect(Collectors.joining(", ")); 
// 使用逗號和空格作為分隔符連線名字  
  
        System.out.println("Employee names joined: " + namesJoined);

輸出結果:

Employee names joined: Alice, Bob, Charlie, David

3.7 分組(partitioningBy/groupingBy)

List<Employee> employees = Arrays.asList(  
                new Employee("Alice", 5000.0, "Development"),  
                new Employee("Bob", 6000.0, "Management"),  
                new Employee("Charlie", 5500.0, "Development"),  
                new Employee("David", 6500.0, "Management"),  
                new Employee("Eve", 5200.0, "HR")  
        );  
  
        // 使用 partitioningBy 根據薪水是否高於6000進行分割槽  
        Predicate<Employee> salaryAbove6000 = employee -> employee.getSalary() > 6000;  
        Map<Boolean, List<Employee>> partitionedBySalary = employees.stream()  
                .collect(Collectors.partitioningBy(salaryAbove6000));  
        System.out.println("Employees partitioned by salary > 6000: " + partitionedBySalary);  
  
        // 使用 groupingBy 根據部門進行分組  
        Map<String, List<Employee>> groupedByDepartment = employees.stream()  
                .collect(Collectors.groupingBy(Employee::getDepartment));  
        System.out.println("Employees grouped by department: " + groupedByDepartment);  
    }
  • Collectors.partitioningBy 方法用於根據提供的謂詞(Predicate)對流中的元素進行分割槽。
  • Collectors.groupingBy 方法用於根據提供的分類函式對流中的元素進行分組。在這個例子中,分類函式是 Employee::getDepartment,它根據員工的部門對員工進行分組。結果是一個對映,其中鍵是部門名稱,值是對應部門的員工列表。

4 其他操作:sequential(順序流)/parallel(並行流)

parallel和sequential是用來指定流的執行模式的方法。這兩種模式決定了流中的元素是如何被處理的。

4.1parallel(並行流,基於ForkJoinPool)

呼叫parallelStream()或者對一個已經存在的流呼叫parallel()時,這個流以並行方式執行操作。並行流會嘗試利用多個執行緒來同時處理多個元素,以提高處理速度。並行流是基於Java的ForkJoinPool實現的,它是一個特殊的執行緒池,適合執行可以並行處理的任務。

4.2sequential(順序流)

對一個流呼叫sequential()時,這個流以順序方式執行操作。順序流中的元素按照它們在資料來源中出現的順序逐個進行處理。順序流是在單個執行緒中執行的,因此不存線上程安全問題。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  
  
 // 使用順序流  
System.out.println("Sequential Stream:");  
numbers.stream()  
           .sequential() // 預設就是順序流,這裏顯式指定  
           .forEach(n -> {  
               System.out.print(n + " ");  
               try {  
                   Thread.sleep(100);
// 耗時操作  
               } catch (InterruptedException e) {  
                   e.printStackTrace();  
               }  
           });  
System.out.println("\n");  
  
// 使用並行流  
System.out.println("Parallel Stream:");  
numbers.stream()  
           .parallel()  
           .forEach(n -> {  
               System.out.print(n + " ");  
               try {  
                   Thread.sleep(100); 
// 耗時操作  
               } catch (InterruptedException e) {  
                   e.printStackTrace();  
               }  
           });  
System.out.println("\n");

順序流的可能輸出(每次執行都應該相同):

Sequential Stream:  
1 2 3 4 5 6 7 8 9 10

並行流的可能輸出(每次執行都可能不同):

Parallel Stream:  
4 2 6 8 1 3 9 5 7 10

不應該在forEach操作中執行有副作用的操作(比如修改共享變數),因為並行流不保證操作的順序性。如果需要收集結果或者執行有狀態的操作,應該使用像collect這樣的終端操作來代替。


0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.