封面圖上流動的「Stream」字樣,正是 Java 8 以來最革命性的特性之一!你是否還在寫冗長的 for 循環遍歷集合?是否為過濾、排序、聚合數據寫一堆重復代碼?Stream 流的出現,以聲明式編程風格將復雜的集合操作濃縮為一行代碼,不僅可讀性更強,還能輕松實現并行處理。本文將從 Stream 的核心概念(中間操作 / 終止操作)講起,結合 10 + 實戰案例(過濾去重、分組統計、鏈式調用),帶你吃透 Stream 流的精髓,讓代碼更優雅、更高效!
Stream API ?萬物皆可一行代碼
Stream專門針對集合的各種操作提供各種非常便利,簡單,高效的API,
Stream API主要是通過Lambda表達式完成,極大的提高了程序的效率和可讀性,
同時Stram API中自帶的并行流使得并發處理集合的門檻再次降低,使用Stream API編程無需多寫
怎樣使用Stream流?
1. 獲取流-->? 2. 對流進行操作-->3.結束對流的操作
獲取流的方式
集合類可通過
Collection.stream()
或Collection.parallelStream()
獲取流;數組可通過
Arrays.stream(T[] array)
或Stream.of()
轉換。IO 相關流如
Files.walk()
或BufferedReader.lines()
也可生成流。
中間操作(Intermediate Operations)
中間操作返回新流,支持鏈式調用,操作延遲執行(lazy)。
映射操作
map
:元素一對一轉換。List<String> names = students.stream().map(Student::getName).collect(Collectors.toList());
flatMap
:扁平化多維結構。List<String> words = lines.stream().flatMap(line -> Arrays.stream(line.split(" "))).collect(Collectors.toList());
過濾與去重
filter
:保留符合謂詞的元素。List<Student> adults = students.stream().filter(s -> s.getAge() > 18).collect(Collectors.toList());
distinct
:去除重復元素(依賴equals
方法)。
排序與裁剪
sorted
:自然排序或自定義比較器。
在Java流(Stream) API中,sorted
方法用于對流中的元素進行排序操作。它支持兩種重載形式,可以根據需要選擇無參數或傳入自定義比較器(Comparator
)。下面我將逐步解釋其用法,并提供真實可靠的示例代碼,幫助您理解如何高效實現排序。
1. 無參數sorted方法
- 當調用
sorted()
方法而不傳遞任何參數時,要求流中的元素必須實現Comparable<T>
接口。這適用于元素類已經定義了自然排序順序的情況。 - 示例:如果元素類(如
String
或自定義類)實現了Comparable
,則可以直接使用。List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream().sorted() // 依賴String的Comparable實現.forEach(System.out::println);
Alice
,Bob
,Charlie
。
2. 帶Comparator參數的sorted方法
方法簽名:
Stream<T> sorted(Comparator<? super T> comparator)
這允許您傳入一個自定義比較器來定義排序規則,特別適用于元素類未實現
Comparable
或需要特定排序邏輯的場景。使用lambda表達式可以簡潔地定義比較器。
- 示例:基于對象的屬性(如年齡)排序。
List<Student> students = Arrays.asList(new Student("Alice", 20),new Student("Bob", 18),new Student("Charlie", 22) );// 使用lambda表達式排序:按年齡從小到大 students.stream().sorted((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge())).forEach(System.out::println);
Bob(18)
,Alice(20)
,Charlie(22)
)。
- 示例:基于對象的屬性(如年齡)排序。
更推薦使用
Comparator
的工廠方法(如Comparator.comparingInt
),以提高代碼可讀性和健壯性。- 示例:使用
Comparator.comparingInt
優化排序。students.stream().sorted(Comparator.comparingInt(Student::getAge)) // 等價于lambda,但更簡潔.forEach(System.out::println);
- 示例:使用
3. 注意事項和最佳實踐
- 性能考慮:
sorted
方法是一個有狀態操作,可能影響流性能。對于大數據集,建議在排序前使用parallelStream()
(如果線程安全)。 - 降序排序:可以通過
Comparator.reversed()
實現降序。students.stream().sorted(Comparator.comparingInt(Student::getAge).reversed()).forEach(System.out::println);
Charlie(22)
,Alice(20)
,Bob(18)
)。 - 多字段排序:使用
Comparator.thenComparing
實現復合排序。students.stream().sorted(Comparator.comparing(Student::getName).thenComparingInt(Student::getAge)).forEach(System.out::println);
limit
/skip
:截取前 N 項或跳過前 N 項。
調試輔助
peek
:遍歷元素但不終結流,常用于調試。students.stream().peek(s -> System.out.println("Processing: " + s.getName())).collect(Collectors.toList());
終結操作(Terminal Operations)
終結操作觸發流處理并返回結果或副作用。
遍歷與聚合
forEach
:遍歷元素。students.stream().forEach(System.out::println);
count
:統計元素數量。long count = students.stream().count();
min
/max
:需傳入比較器。Optional<Student> oldest = students.stream().max(Comparator.comparingInt(Student::getAge));
匹配與查找
anyMatch
/allMatch
/noneMatch
:檢查元素是否匹配條件。boolean hasAdult = students.stream().anyMatch(s -> s.getAge() > 18);
findFirst
/findAny
:返回首個或任意元素(并行流中效率更高)。
歸約與收集
2. 使用 Collectors
簡化操作
Collectors
類提供了許多內置轉換器,如 toList()
、toSet()
等,可以直接傳入 collect
方法。例如:
3. groupingBy
分組操作
groupingBy
類似于 SQL 中的 GROUP BY,用于按指定條件分組元素。它有多個參數版本:
4. toMap
轉換操作
toMap
用于將流轉換為 Map
,需指定鍵和值的映射函數。例如:
// 將學生列表轉換為 Map<年齡, 學生對象>
Map<Integer, Student> map = students.stream().collect(Collectors.toMap(Student::getAge, s -> s));
5. 基本數據類型流的限制
對于基本數據類型流(如 IntStream
、LongStream
、DoubleStream
),沒有 collect
方法。這是因為基本類型涉及裝箱(boxing)和拆箱(unboxing)操作,Java SDK 未將其集成到流收集機制中。替代方案是:
reduce
:聚合元素。Optional<Integer> totalAge = students.stream().map(Student::getAge).reduce(Integer::sum);
collect
:轉換為集合或分組。collect
是 Java Stream API 中的一個終端操作,用于將流中的元素收集到一個容器中(如集合或映射)。下面我將基于您提供的信息,逐步解釋collect
的使用方法、常見場景和注意事項,確保內容真實可靠(基于 Java 8 及以上版本的規范)。1.
collect
方法的基本簽名collect
方法有兩個主要重載形式:- 第一個重載:接受三個參數(supplier、accumulator 和 combiner),用于自定義收集邏輯。
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);
supplier
:提供一個新容器(如ArrayList::new
)。accumulator
:將元素添加到容器中(如List::add
)。combiner
:在并行流中合并多個容器(如List::addAll
)。
- 第二個重載:接受一個
Collector
對象,簡化常見操作。<R, A> R collect(Collector<? super T, A, R> collector);
Collectors
工具類中的靜態方法作為參數,因為它是更簡潔的方式。 - 轉換為列表:將流元素收集到
List
中。List<Integer> list = integers.stream().collect(Collectors.toList());
ArrayList
。 - 轉換為集合:將流元素收集到
Set
中。Set<Integer> set = integers.stream().collect(Collectors.toSet());
HashSet
。 - 單參數版本:按分類函數分組,返回
Map
。// 按學生年齡分組,返回 Map<Integer, List<Student>> Map<Integer, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getAge));
HashMap
作為容器。 - 多參數版本:支持自定義容器和下游收集器。
- 兩個參數:指定分類函數和下游收集器(如
toList()
)。 - 三個參數:添加容器類型(如
TreeMap::new
)。 例如,您提到的例子:按年齡分組,但只收集學生姓名而非整個對象。
// 按年齡分組,每組存儲學生姓名列表 Map<Integer, List<String>> map = students.stream().collect(Collectors.groupingBy(Student::getAge,Collectors.mapping(Student::getName, Collectors.toList())));
- 第一個參數
Student::getAge
是分類依據。 - 第二個參數
Collectors.mapping(...)
是下游收集器,其中mapping
將每個學生映射到姓名,再用toList()
收集為列表。 如果省略容器參數,默認仍為HashMap
。
- 兩個參數:指定分類函數和下游收集器(如
- 第一個參數
Student::getAge
定義鍵(key)。 - 第二個參數
s -> s
定義值(value),這里直接使用學生對象。 注意事項: - 如果鍵重復,會拋出
IllegalStateException
。解決方法是添加第三個參數(合并函數),如(oldValue, newValue) -> oldValue
保留第一個值。 - 默認使用
HashMap
,但可以通過第四個參數指定其他Map
實現(如TreeMap::new
)。 - 使用
toArray()
方法轉換為數組。int[] array = intStream.toArray();
- 或者,先將流轉換為對象流(如
boxed()
),再使用collect
。List<Integer> list = intStream.boxed().collect(Collectors.toList());
- 特殊轉換
toArray
:轉為數組,可指定類型。Student[] array = students.stream().toArray(Student[]::new);
并行流注意事項
通過 parallelStream()
可啟用并行處理,但需確保操作無狀態且避免共享變量。以下場景適合并行流:
- 數據量較大且處理耗時。
- 任務可獨立分片(如過濾、映射)。
List<Student> adults = students.parallelStream().filter(s -> s.getAge() > 18).collect(Collectors.toList());