<think>
我们被要求使用Stream API将List转换为Map。根据引用[2]和引用[3],我们可以使用`Collectors.toMap`或`Collectors.groupingBy`来实现。
主要两种转换方式:
1. 使用`Collectors.toMap`:将每个元素映射为键值对,适合一对一的映射(即每个键对应一个值)。
2. 使用`Collectors.groupingBy`:将元素按某个属性分组,适合一对多的映射(即每个键对应一个列表的值)。
下面分别详细说明:
方法一:使用`Collectors.toMap`(键唯一)
如果List中的元素转换为Map时,键是唯一的,那么可以使用`toMap`。如果键有重复,则会抛出`IllegalStateException`异常,除非我们提供合并函数(merge function)来解决冲突。
基本语法:
```java
Map<K, V> map = list.stream()
.collect(Collectors.toMap(
keyMapper, // 函数:从元素中提取键
valueMapper, // 函数:从元素中提取值
mergeFunction // 可选:当键冲突时,如何合并两个值(例如,如果两个元素有相同的键,则可以选择保留其中一个或合并)
));
```
示例1:将对象列表转换为以对象某个属性为键,对象本身为值的Map(假设键唯一)
```java
List<Person> personList = ...; // 假设Person类有id和name
Map<Long, Person> personMap = personList.stream()
.collect(Collectors.toMap(
Person::getId, // 键:Person对象的id
person -> person // 值:对象本身
));
```
示例2:如果键可能重复,我们可以指定合并规则(例如,保留先出现的,或后出现的,或合并)
```java
// 假设有两个Person对象可能有相同的id,我们想要保留后出现的
Map<Long, Person> personMap = personList.stream()
.collect(Collectors.toMap(
Person::getId,
person -> person,
(existing, replacement) -> replacement // 当有重复键时,用新值替换旧值
));
```
方法二:使用`Collectors.groupingBy`(键可以重复,一个键对应多个值)
如果我们希望将相同键的元素收集到一个列表中,那么应该使用`groupingBy`。
基本语法:
```java
Map<K, List<V>> map = list.stream()
.collect(Collectors.groupingBy(
keyMapper, // 提取键
Collectors.mapping(valueMapper, Collectors.toList()) // 可选:对每个分组中的元素进行映射并收集为列表
));
```
实际上,如果直接使用`groupingBy`只指定键提取函数,那么值默认就是该元素本身,并且收集到一个列表中。
示例3:将相同城市的Person对象分组(键为城市,值为该城市的Person列表)
```java
Map<String, List<Person>> peopleByCity = personList.stream()
.collect(Collectors.groupingBy(Person::getCity));
```
示例4:如果我们想要分组后对元素进行进一步转换(例如,只取名字而不是整个对象)
```java
Map<String, List<String>> cityToNames = personList.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.mapping(Person::getName, Collectors.toList())
));
```
引用[2]中展示了使用`groupingBy`将List转换为`Map<String, List<Region>>`,即按父代码分组。
引用[3]中展示了使用`toMap`将List转换为`Map<String, ProjectLineAreaVo>`,其中键由两个属性拼接而成。
注意事项:
1. 使用`toMap`时,如果键重复且没有指定合并函数,会抛出异常。
2. 使用`groupingBy`时,即使键重复也不会抛出异常,而是将相同键的元素收集到列表中。
3. 如果使用`toMap`并且希望值为列表(即一对多),那么不能直接使用`toMap`,而应该使用`groupingBy`。
因此,根据需求选择合适的方法:
- 一对一(键唯一):`toMap`
- 一对多(键重复,需要分组):`groupingBy`
另外,引用[3]中还有将List中元素的某个属性收集为List的例子:
```java
List<Long> idList = list.stream().map(BasSpacePackage::getId).collect(Collectors.toList());
```
但这不是转换为Map,而是转换为List。
总结:
使用Stream API将List转换为Map有两种主要方式:
1. 使用`Collectors.toMap`:适合将每个元素转换为唯一的键值对。
2. 使用`Collectors.groupingBy`:适合将元素按键分组,形成键到列表的映射。
根据具体需求选择方法,并注意处理键重复的情况(在`toMap`中必须提供合并函数)。
下面我们给出几个常见的转换示例:
示例5:将List转换为Map,其中键是id,值是name(假设id唯一)
```java
Map<Long, String> idToNameMap = personList.stream()
.collect(Collectors.toMap(
Person::getId,
Person::getName
));
```
示例6:将List转换为Map,其中键是城市,值是该城市的所有Person的年龄列表
```java
Map<String, List<Integer>> cityToAges = personList.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.mapping(Person::getAge, Collectors.toList())
));
```
示例7:如果键可能重复,且我们想用`toMap`并取第一个值(忽略后面的重复键)
```java
Map<Long, Person> firstPersonMap = personList.stream()
.collect(Collectors.toMap(
Person::getId,
person -> person,
(first, second) -> first // 遇到重复id时,保留第一个
));
```
示例8:使用`groupingBy`并指定下游收集器为计数(统计每个城市的数量)
```java
Map<String, Long> countByCity = personList.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.counting()
));
```
希望这些示例能帮助您理解如何使用Stream API将List转换为Map。
</think>
在 Java Stream API 中将 `List` 转换为 `Map` 主要有两种核心方式,分别使用 `Collectors.toMap()` 和 `Collectors.groupingBy()`。以下是详细方法和示例:
---
### 一、使用 `Collectors.toMap()`(一对一映射)
#### 1. **基础转换(键唯一)**
```java
List<Person> personList = Arrays.asList(
new Person(1, "Alice"),
new Person(2, "Bob")
);
// 键:id,值:name
Map<Integer, String> idToNameMap = personList.stream()
.collect(Collectors.toMap(
Person::getId, // 键提取函数
Person::getName // 值提取函数
));
// 结果:{1="Alice", 2="Bob"}
```
#### 2. **处理键冲突(重复键合并)**
```java
List<Person> duplicateList = Arrays.asList(
new Person(1, "Alice"),
new Person(1, "Alex") // 相同ID
);
// 合并策略:保留后出现的值
Map<Integer, String> mergedMap = duplicateList.stream()
.collect(Collectors.toMap(
Person::getId,
Person::getName,
(oldVal, newVal) -> newVal // 冲突处理函数
));
// 结果:{1="Alex"}
```
#### 3. **键为复合值**
```java
Map<String, Person> compositeKeyMap = personList.stream()
.collect(Collectors.toMap(
p -> p.getId() + "_" + p.getName(), // 复合键
p -> p, // 值=对象本身
(oldVal, newVal) -> newVal // 冲突处理
));
// 结果:{"1_Alice": Person@123, "2_Bob": Person@456}
```
---
### 二、使用 `Collectors.groupingBy()`(一对多分组)
#### 1. **基础分组**
```java
List<Person> groupList = Arrays.asList(
new Person(1, "Alice", "Tech"),
new Person(2, "Bob", "Tech"),
new Person(3, "Charlie", "HR")
);
// 按部门分组
Map<String, List<Person>> deptMap = groupList.stream()
.collect(Collectors.groupingBy(Person::getDepartment));
/* 结果:
{
"Tech": [Person(1,"Alice"), Person(2,"Bob")],
"HR": [Person(3,"Charlie")]
}
*/
```
#### 2. **分组后值转换**
```java
// 按部门分组,值提取为名称列表
Map<String, List<String>> deptToNames = groupList.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.mapping(Person::getName, Collectors.toList())
));
// 结果:{"Tech": ["Alice","Bob"], "HR": ["Charlie"]}
```
#### 3. **分组统计**
```java
// 按部门统计人数
Map<String, Long> deptCount = groupList.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.counting()
));
// 结果:{"Tech":2, "HR":1}
```
---
### 三、注意事项
1. **键唯一性问题**
- 使用 `toMap()` 时需确保键唯一,否则需提供合并函数(如示例2)。
- 使用 `groupingBy()` 时自动处理重复键(值转为集合)。
2. **空指针处理**
若键可能为 `null`,需提前过滤:
```java
Map<Integer, String> safeMap = personList.stream()
.filter(p -> p.getId() != null)
.collect(Collectors.toMap(...));
```
3. **自定义Map实现**
指定具体 `Map` 实现类:
```java
Map<Integer, Person> linkedMap = personList.stream()
.collect(Collectors.toMap(
Person::getId,
p -> p,
(oldVal, newVal) -> newVal,
LinkedHashMap::new // 保持插入顺序
));
```
---
### 四、方法对比
| 场景 | 推荐方法 | 示例 |
|--------------------------|------------------------|---------------------------|
| 键值一对一(键唯一) | `Collectors.toMap()` | 用户ID → 用户名 |
| 键值一对多(分组) | `Collectors.groupingBy()` | 部门 → 员工列表 |
| 键冲突需合并 | `toMap()` + 合并函数 | 相同ID取最新值 |
| 分组后聚合操作 | `groupingBy()` + 下游收集器 | 统计部门人数 |
> 📌 **最佳实践**:
> - 键唯一时优先用 `toMap()`(更简洁)[^2]
> - 分组统计用 `groupingBy()` + `mapping()`/`counting()`[^3]
> - 复合键用 Lambda 表达式拼接(如 `k1 + "_" + k2`)[^3]
---