Java Collectors
Collectors 類別提供許多預設的收集器,用於將 Stream 結果收集到各種容器中。
收集到集合
import java.util.stream.Collectors;
List<String> names = stream.collect(Collectors.toList());
Set<String> uniqueNames = stream.collect(Collectors.toSet());
// 指定集合類型
ArrayList<String> arrayList = stream.collect(
Collectors.toCollection(ArrayList::new));
LinkedHashSet<String> linkedHashSet = stream.collect(
Collectors.toCollection(LinkedHashSet::new));
TreeSet<String> treeSet = stream.collect(
Collectors.toCollection(TreeSet::new));
// Java 10+
List<String> unmodifiableList = stream.collect(Collectors.toUnmodifiableList());
Set<String> unmodifiableSet = stream.collect(Collectors.toUnmodifiableSet());
收集到 Map
toMap
// 基本用法
Map<Integer, String> map = users.stream()
.collect(Collectors.toMap(
User::getId, // key
User::getName // value
));
// key 重複時的處理
Map<String, User> map = users.stream()
.collect(Collectors.toMap(
User::getName,
user -> user,
(existing, replacement) -> existing // 保留舊值
));
// 指定 Map 類型
LinkedHashMap<Integer, String> linkedMap = users.stream()
.collect(Collectors.toMap(
User::getId,
User::getName,
(a, b) -> a,
LinkedHashMap::new
));
物件轉 Map
// ID -> 物件
Map<Integer, User> userById = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
// 屬性 -> 物件(使用 Function.identity())
Map<String, User> userByEmail = users.stream()
.collect(Collectors.toMap(User::getEmail, u -> u));
字串連接
// 基本連接
String result = names.stream()
.collect(Collectors.joining()); // "AliceBobCharlie"
// 使用分隔符
String result = names.stream()
.collect(Collectors.joining(", ")); // "Alice, Bob, Charlie"
// 前綴、分隔符、後綴
String result = names.stream()
.collect(Collectors.joining(", ", "[", "]")); // "[Alice, Bob, Charlie]"
分組
groupingBy
// 基本分組
Map<String, List<User>> byCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
// 多級分組
Map<String, Map<String, List<User>>> byCityAndDept = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.groupingBy(User::getDepartment)
));
// 分組並計數
Map<String, Long> countByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.counting()
));
// 分組並求和
Map<String, Integer> ageByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.summingInt(User::getAge)
));
// 分組並取得其他資訊
Map<String, Optional<User>> oldestByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.maxBy(Comparator.comparing(User::getAge))
));
// 分組並轉換
Map<String, List<String>> namesByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.mapping(User::getName, Collectors.toList())
));
// 指定 Map 類型
TreeMap<String, List<User>> sortedByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
TreeMap::new,
Collectors.toList()
));
分區
partitioningBy
分成兩組(true/false):
// 基本分區
Map<Boolean, List<User>> partition = users.stream()
.collect(Collectors.partitioningBy(u -> u.getAge() >= 18));
List<User> adults = partition.get(true);
List<User> minors = partition.get(false);
// 分區並計數
Map<Boolean, Long> count = users.stream()
.collect(Collectors.partitioningBy(
u -> u.getAge() >= 18,
Collectors.counting()
));
數值統計
// 計數
long count = stream.collect(Collectors.counting());
// 求和
int sum = stream.collect(Collectors.summingInt(User::getAge));
long sum = stream.collect(Collectors.summingLong(User::getScore));
double sum = stream.collect(Collectors.summingDouble(User::getBalance));
// 平均
Double avg = stream.collect(Collectors.averagingInt(User::getAge));
Double avg = stream.collect(Collectors.averagingDouble(User::getBalance));
// 統計摘要
IntSummaryStatistics stats = users.stream()
.collect(Collectors.summarizingInt(User::getAge));
// stats.getCount(), stats.getSum(), stats.getMin(), stats.getMax(), stats.getAverage()
最大/最小
Optional<User> oldest = users.stream()
.collect(Collectors.maxBy(Comparator.comparing(User::getAge)));
Optional<User> youngest = users.stream()
.collect(Collectors.minBy(Comparator.comparing(User::getAge)));
歸約
Optional<String> concat = names.stream()
.collect(Collectors.reducing((a, b) -> a + b));
String concat = names.stream()
.collect(Collectors.reducing("", (a, b) -> a + b));
mapping / flatMapping
轉換後再收集:
// mapping
List<String> names = users.stream()
.collect(Collectors.mapping(
User::getName,
Collectors.toList()
));
// 分組後 mapping
Map<String, List<String>> namesByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.mapping(User::getName, Collectors.toList())
));
// flatMapping(Java 9+)
Map<String, Set<String>> skillsByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.flatMapping(
u -> u.getSkills().stream(),
Collectors.toSet()
)
));
filtering(Java 9+)
過濾後再收集:
Map<String, List<User>> adultsByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.filtering(
u -> u.getAge() >= 18,
Collectors.toList()
)
));
teeing(Java 12+)
同時使用兩個收集器:
// 同時取得最大和最小
var result = users.stream()
.collect(Collectors.teeing(
Collectors.maxBy(Comparator.comparing(User::getAge)),
Collectors.minBy(Comparator.comparing(User::getAge)),
(oldest, youngest) -> Map.of("oldest", oldest, "youngest", youngest)
));
// 同時計算總和和計數
var result = numbers.stream()
.collect(Collectors.teeing(
Collectors.summingInt(Integer::intValue),
Collectors.counting(),
(sum, count) -> sum / (double) count // 手動計算平均
));
collectingAndThen
收集後再轉換:
// 收集後轉為不可變
List<String> immutable = names.stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
// 收集後取第一個
String first = names.stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> list.isEmpty() ? null : list.get(0)
));
自訂 Collector
// 使用 Collector.of
Collector<String, StringBuilder, String> joiningCollector =
Collector.of(
StringBuilder::new, // supplier
StringBuilder::append, // accumulator
(sb1, sb2) -> sb1.append(sb2), // combiner
StringBuilder::toString // finisher
);
String result = stream.collect(joiningCollector);
常用 Collectors 一覽
| Collector | 說明 |
|---|---|
toList() | 收集到 List |
toSet() | 收集到 Set |
toMap() | 收集到 Map |
toCollection() | 收集到指定集合 |
joining() | 連接字串 |
groupingBy() | 分組 |
partitioningBy() | 分區(二分) |
counting() | 計數 |
summingInt/Long/Double() | 求和 |
averagingInt/Long/Double() | 平均 |
maxBy() / minBy() | 最大/最小 |
mapping() | 轉換後收集 |
filtering() | 過濾後收集 |
flatMapping() | 展開後收集 |
teeing() | 同時使用兩個收集器 |
collectingAndThen() | 收集後轉換 |
重點整理
toList()、toSet()、toMap()是最常用的收集器groupingBy()用於分組,可以巢狀使用partitioningBy()將資料分成兩組joining()用於連接字串mapping()和filtering()可以在收集前轉換/過濾teeing()可以同時執行兩個收集操作collectingAndThen()可以對收集結果做後處理