使用Java Stream,将集合转换为一对一Map

在日常的开发工作中,我们经常使用到Java Stream,特别是Stream API中提供的Collectors.toList()收集器,

但有些场景下,我们需要将集合转换为Map,这时候就需要使用到Stream API中提供的另一个收集器:

Collectors.toMap,它可以将流中的元素映射为键值对,并收集到一个Map中。

1. 三种主要的重载方法

Collectors.toMap有3种重载方法,分别是:

1)两个参数的重载方法(最简单的形式)

/* by 01130.hk - online tools website : 01130.hk/zh/linuxcmd.html */
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper) {
	return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

2)三个参数的重载方法(包含冲突处理)

/* by 01130.hk - online tools website : 01130.hk/zh/linuxcmd.html */
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction) {
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

3)四个参数的重载方法(指定Map实现)

public static <T, K, U, M extends Map<K, U>>
   Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                            Function<? super T, ? extends U> valueMapper,
                            BinaryOperator<U> mergeFunction,
                            Supplier<M> mapSupplier) {
    BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),
                                          valueMapper.apply(element), mergeFunction);
    return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}

接下来,我们结合使用示例详细讲解。

2. 使用示例

2.1 将对象的某些属性转换为Map

假设有一个城市列表,需要将其转换为Map,其中Key为城市ID、Value为城市名称,转换方法如下所示:

@Getter
@Setter
public class City {
    private Integer cityId;

    private String cityName;

    public City(Integer cityId, String cityName) {
        this.cityId = cityId;
        this.cityName = cityName;
    }
}
List<City> cityList = Arrays.asList(
        new City(1, "北京"),
        new City(2, "上海"),
        new City(3, "广州"),
        new City(4, "深圳")
);
Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println(cityMap);

输出结果:

2.2 将对象列表转换为Map(ID -> 对象)

仍然使用上面的城市列表,需要将其转换为Map,其中Key为城市ID、Value为城市对象,转换方法如下所示:

List<City> cityList = Arrays.asList(
        new City(1, "北京"),
        new City(2, "上海"),
        new City(3, "广州"),
        new City(4, "深圳")
);
Map<Integer, City> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, city -> city));
City city = cityMap.get(1);
System.out.println("城市ID: " + city.getCityId());
System.out.println("城市名称: " + city.getCityName());

输出结果如下所示:

城市ID: 1
城市名称: 北京

上面的写法等价于:

Map<Integer, City> cityMap = cityList.stream()
    	.collect(Collectors.toMap(City::getCityId, Function.identity()));

因为Function.identity()内部实现是下面这样的:

static <T> Function<T, T> identity() {
    return t -> t;
}

2.3 键冲突处理

假设上面的城市列表中有一个ID重复的城市:

List<City> cityList = Arrays.asList(
        new City(1, "北京"),
        new City(2, "上海"),
        new City(3, "广州"),
        new City(4, "深圳"),
        new City(4, "天津")
);
Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println("城市ID: 4, 城市名称: " + cityMap.get(4));

此时运行代码,会抛出java.lang.IllegalStateException异常,如下图所示:

有3种常见的键冲突处理方式,分别是保留旧值、使用新值和合并值,接下来一一讲解。

1)方式一:保留旧值

Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> oldValue));

输出结果:

城市ID: 4, 城市名称: 深圳

2)方式二:使用新值

Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> newValue));

输出结果:

城市ID: 4, 城市名称: 天津

3)方式三:合并值

Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName, 
        		(oldValue, newValue) -> oldValue + ", " + newValue));

输出结果:

城市ID: 4, 城市名称: 深圳, 天津

2.4 数据分组聚合

假设有一个销售记录列表,需要将其转换为Map,其中Key为销售员、Value为该销售员的总销售额,转换方法如下所示:

@Getter
@Setter
public class SalesRecord {
    private String salesPerson;

    private BigDecimal amount;

    public SalesRecord(String salesPerson, BigDecimal amount) {
        this.salesPerson = salesPerson;
        this.amount = amount;
    }
}
List<SalesRecord> salesRecordList = Arrays.asList(
        new SalesRecord("张三", new BigDecimal("1000")),
        new SalesRecord("李四", new BigDecimal("2000")),
        new SalesRecord("张三", new BigDecimal("980"))
);

Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
        .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::add));
System.out.println(salesRecordMap);

输出结果:

上面的例子是销售额累加,也可以只取最小值:

Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
        .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::min));

此时的输出结果:

或者只取最大值:

Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
        .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::max));

此时的输出结果:

2.5 指定Map实现

默认情况下,Collectors.toMap是将结果收集到HashMap中,如果有需要,我们也可以指定成TreeMap或者LinkedHashMap。

如果想要保持插入顺序,可以指定使用LinkedHashMap:

List<City> cityList = Arrays.asList(
        new City(2, "上海"),
        new City(1, "北京"),
        new City(4, "深圳"),
        new City(3, "广州")
);
Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName,
                (existing, replacement) -> existing, LinkedHashMap::new));
System.out.println(cityMap);

输出结果:

如果想要按键排序,可以指定使用TreeMap:

List<City> cityList = Arrays.asList(
        new City(2, "上海"),
        new City(1, "北京"),
        new City(4, "深圳"),
        new City(3, "广州")
);
Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName,
                (existing, replacement) -> existing, TreeMap::new));
System.out.println(cityMap);

输出结果:

3. 注意事项

3.1 空异常

如果valueMapper中取出的值有null值,会抛出java.lang.NullPointerException异常,如下示例:

List<City> cityList = Arrays.asList(
        new City(1, "北京"),
        new City(2, "上海"),
        new City(3, "广州"),
        new City(4, "深圳"),
        new City(5, null)
);
Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println(cityMap);

运行以上代码会抛出异常,如下图所示:

有两种解决方案,第一种解决方案是过滤null值:

Map<Integer, String> cityMap = cityList.stream()
        .filter(city -> city.getCityName() != null)
        .collect(Collectors.toMap(City::getCityId, City::getCityName));

第二种解决方案是提供默认值:

Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId,
                city -> Optional.ofNullable(city.getCityName()).orElse("未知")));

3.2 键重复异常

如果出现重复键,且没有提供mergeFunction参数,会抛出java.lang.IllegalStateException异常,如下示例:

List<City> cityList = Arrays.asList(
        new City(1, "北京"),
        new City(2, "上海"),
        new City(3, "广州"),
        new City(4, "深圳"),
        new City(4, "天津")
);
Map<Integer, String> cityMap = cityList.stream()
        .collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println(cityMap);

运行以上代码会抛出异常,如下图所示:

解决方案见本篇文章2.3 键冲突处理部分。

4. 总结

Collectors.toMap是Stream API中提供的一个非常方便的收集器,它可以将流中的元素映射为键值对,并收集到一个Map中。

它适用于一对一映射的场景,但在使用时,要注意避免java.lang.NullPointerException异常和

java.lang.IllegalStateException异常。

文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!

<think> 首先,用户的问题是:“我想查找使用Java Stream集合转换Map的方法 请问使用Java Stream集合转换Map的实现方法”。他们提供了几个引用,这些引用似乎是从之前的上下文或站内引用来的,涉及到Java Stream API的使用,特别是map方法和Collectors.toMap()。 关键点: - 用户想要知道如何使用Java Stream集合转换Map。 - 引用[3]和[5]直接提到了使用Collectors.toMap()。 - 引用[4]讨论了生产级实现中的细节。 - 引用[1]和[2]是关于map方法的一般使用。 系统指令: - 所有行内数学表达式使用$...$格式(但这里可能不涉及数学,所以可能不需要)。 - 独立公式使用$$...$$格式(同样,可能不适用)。 - LaTeX语法正确(在代码块中,不需要LaTeX)。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如[^1]。 - 用户可见层:回答结构清晰,逐步解决问题,保证真实可靠。 - 最后添加相关问题。 我的回答需要: 1. 结构化:逐步解释如何实现。 2. 真实可靠:基于Java标准库,参考提供的引用。 3. 使用中文。 4. 在引用段落末尾添加引用标识。 5. 生成相关问题。 核心方法是使用Java Stream API的Collectors.toMap()方法。步骤: - 开始一个Stream(例如,list.stream())。 - 使用collect(Collectors.toMap(keyMapper, valueMapper))。 - 处理键冲突(可选)。 - 处理null值(可选)。 从引用中: - [3]给出了一个示例代码。 - [4]讨论了生产级细节如键冲突。 - [5]展示了简单的一行代码转换。 回答结构: 1. 介绍方法:简要说明使用Stream API转换集合Map。 2. 基本步骤:解释如何做。 3. 提供示例代码:基于引用[3]或[5]。 4. 讨论注意事项:如键冲突、null处理。 5. 添加引用:在相关部分添加标识。 6. 生成相关问题。 示例代码: 从引用[3]: ```java import java.util.*; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 35) ); Map<String, Integer> ageByName = people.stream() .collect(Collectors.toMap(Person::getName, Person::getAge)); System.out.println(ageByName); } } class Person { private String name; private int age; // constructors, getters, etc. } ``` 注意事项: - 键冲突:如果键重复,会抛出IllegalStateException。可以使用第三个参数来解决冲突,如(toMap(key, value, mergeFunction))。 - null键:可能抛出NullPointerException,需要处理。 - Map实现:默认是HashMap,可以使用第四个参数指定,如LinkedHashMap。 生成相关问题: 基于主题,如“如何处理键冲突?”、“如何转换复杂对象?”等。 最终回答结构: - 开头介绍。 - 步骤解释。 - 代码示例。 - 注意事项。 - 相关问题。 </think> ### 使用 Java Stream集合转换Map 的实现方法 在 Java 中,使用 Stream API 将集合(如 List)转换Map 是一种高效且优雅的方式,主要通过 `Collectors.toMap()` 方法实现。以下是详细步骤和示例,确保代码可靠且适用于生产环境。 #### 1. **基本实现步骤** - **步骤 1**: 将集合转换Stream,例如 `list.stream()`。 - **步骤 2**: 使用 `collect(Collectors.toMap(...))` 方法: - **参数 1 (键提取器)**: 指定 Map 的键如何从元素中提取,例如 `Person::getName`。 - **参数 2 (值提取器)**: 指定 Map 的值如何从元素中提取,例如 `Person::getAge`。 - **步骤 3**: 处理结果,例如收集到 Map 变量中。 - 这种方法简洁易读,特别适用于对象属性到 Map转换[^2]。 #### 2. **基础代码示例** 参考引用[3],以下是一个简单示例,将 `List<Person>` 转换为 `Map<String, Integer>`(键为姓名,值为年龄): ```java import java.util.*; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { // 创建示例集合 List<Person> people = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 35) ); // 使用 Stream 转换Map Map<String, Integer> ageByName = people.stream() .collect(Collectors.toMap( Person::getName, // 键提取器 Person::getAge // 值提取器 )); System.out.println(ageByName); // 输出: {Alice=25, Bob=30, Charlie=35} } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } ``` 此代码通过 `Person::getName` 和 `Person::getAge` 提取键值对,生成一个 `HashMap`[^3]。 #### 3. **生产级注意事项** 在实际应用中,需处理潜在问题以确保健壮性: - **键冲突处理**: 如果键重复(如两个 `Person` 同名),会抛出 `IllegalStateException`。添加第三个参数(合并函数)解决: ```java Map<String, Integer> ageByName = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldValue, newValue) -> oldValue // 保留旧值,或自定义逻辑如 newValue )); ``` [^4] - **空值处理**: 如果键或值为 `null`,可能引发 `NullPointerException`。提前过滤或使用 `Optional`: ```java Map<String, Integer> safeMap = people.stream() .filter(p -> p.getName() != null) // 过滤空键 .collect(Collectors.toMap(Person::getName, Person::getAge)); ``` - **指定 Map 类型**: 默认生成 `HashMap`,可通过第四个参数指定其他实现(如 `LinkedHashMap` 保持顺序): ```java Map<String, Integer> orderedMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldVal, newVal) -> oldVal, LinkedHashMap::new )); ``` [^4] - **对象直接作为值**: 如果需要整个对象作为值(例如 `Map<Integer, Person>`),使用 `Function.identity()`: ```java Map<Integer, Person> idToPerson = people.stream() .collect(Collectors.toMap(Person::getAge, Function.identity())); ``` [^5] #### 4. **优势与适用场景** - **优势**: 代码简洁(一行完成转换),易于维护,且 Stream API 支持链式操作(如过滤、映射后再转换)[^1][^2]。 - **典型场景**: - 数据库查询结果(如 MyBatis/Hibernate 返回的 List) Map,便于通过 ID 快速检索[^5]。 - 数据分组或聚合(如按类别统计)。 - API 响应中简化数据结构。 通过以上方法,您可以高效地将集合转换Map,同时处理生产环境中的常见问题。确保在键可能重复或含空值时添加防御性逻辑[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值