一、Stream流的特點和使用流程
1. 特點
2. 使用流程
二、Stream流的魅力
三、stream流的建立
1. 透過集合建立
2. 透過陣列建立
3. 透過Stream的靜態方法
4. 透過隨機數生成
5. 透過檔案I/O
6. 無限流
7. 透過範圍建立
四、Stream流的應用
1. 中間操作
2. 終端操作
3. 收集操作
4 其他操作:sequential(順序流)/parallel(並行流)
一、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
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這樣的終端操作來代替。