核心概念
- Map (映射): 存储键值对。
Map<K, V>中的K是键,V是值,键是唯一的。 - List (列表): 存储元素的有序集合。
List<T>中的T是列表中元素的类型。
转换的关键在于理解如何将一个结构中的元素映射到另一个结构中。
将 List 转换为 Map
这是最常见的转换场景之一,我们需要将 List 中的某个属性作为 Map 的键,另一个属性作为值。
List 中是普通对象,对象有唯一标识属性
假设我们有一个 User 类,我们想用 List<User> 创建一个 Map,userId 作为键,User 对象本身作为值。
使用 Java 8 Stream API (推荐)
这是最现代、最简洁、最推荐的方式。
import java.util.*;
import java.util.stream.Collectors;
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
public class ListToMapExample {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User(1, "Alice"),
new User(2, "Bob"),
new User(3, "Charlie")
);
// 将 List<User> 转换为 Map<userId, User>
Map<Integer, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, user -> user));
System.out.println(userMap);
// 输出: {1=User{id=1, name='Alice'}, 2=User{id=2, name='Bob'}, 3=User{id=3, name='Charlie'}}
// 如果值就是对象本身,可以使用 Function.identity() 简化
Map<Integer, User> userMapSimple = users.stream()
.collect(Collectors.toMap(User::getId, user -> user)); // 或者 Collectors.toMap(User::getId, Function.identity());
System.out.println(userMapSimple);
}
}
使用传统的 for 循环
这种方式代码量较多,但在不支持 Java 8 的旧项目中依然有效。
List<User> users = Arrays.asList(...); // 同上
Map<Integer, User> userMap = new HashMap<>();
for (User user : users) {
userMap.put(user.getId(), user);
}
处理键冲突
List 中有多个元素的键相同,Collectors.toMap 会抛出 IllegalStateException。
解决方法:提供冲突解决函数
Collectors.toMap 的重载方法允许我们指定当键冲突时如何处理。
import java.util.*;
import java.util.stream.Collectors;
class Product {
private int sku;
private String name;
public Product(int sku, String name) {
this.sku = sku;
this.name = name;
}
public int getSku() {
return sku;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Product{sku=" + sku + ", name='" + name + "'}";
}
}
public class ListToMapConflictExample {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product(101, "Apple"),
new Product(102, "Banana"),
new Product(101, "Green Apple") // SKU 101 重复了
);
// 策略1:保留旧值 (遇到冲突,不覆盖)
Map<Integer, Product> mapKeepOld = products.stream()
.collect(Collectors.toMap(
Product::getSku,
product -> product,
(existingValue, newValue) -> existingValue // 冲突时保留已存在的值
));
System.out.println("保留旧值策略: " + mapKeepOld);
// 输出: 保留旧值策略: {101=Product{sku=101, name='Apple'}, 102=Product{sku=102, name='Banana'}}
// 策略2:保留新值 (遇到冲突,覆盖)
Map<Integer, Product> mapKeepNew = products.stream()
.collect(Collectors.toMap(
Product::getSku,
product -> product,
(existingValue, newValue) -> newValue // 冲突时保留新的值
));
System.out.println("保留新值策略: " + mapKeepNew);
// 输出: 保留新值策略: {101=Product{sku=101, name='Green Apple'}, 102=Product{sku=102, name='Banana'}}
// 策略3:将冲突的值合并为一个 List
// 我们需要将 List<Product> 转换为 Map<Integer, List<Product>>
Map<Integer, List<Product>> mapToList = products.stream()
.collect(Collectors.groupingBy(Product::getSku));
System.out.println("合并为 List 策略: " + mapToList);
// 输出: 合并为 List 策略: {101=[Product{sku=101, name='Apple'}, Product{sku=101, name='Green Apple'}], 102=[Product{sku=102, name='Banana'}]}
}
}
List 中是简单类型,如 String
List 中的元素本身就是值,我们可以将其作为 Map 的值,并生成一个默认的键(如索引)。
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class SimpleListToMapExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
// 将 List<String> 转换为 Map<Integer, String>,Integer 是索引
Map<Integer, String> fruitMapWithIndex = IntStream.range(0, fruits.size())
.boxed()
.collect(Collectors.toMap(
index -> index, // 键是索引
index -> fruits.get(index) // 值是对应的水果
));
System.out.println(fruitMapWithIndex);
// 输出: {0=Apple, 1=Banana, 2=Cherry}
}
}
将 Map 转换为 List
Map 转换为 List 通常有以下几种情况:
- 转换为
List<Map.Entry<K, V>>:保留键值对。 - 转换为
List<K>:只保留键。 - 转换为
List<V>:只保留值。
Map 转换为 List<Map.Entry<K, V>>
这保留了 Map 的完整结构。
import java.util.*;
import java.util.stream.Collectors;
public class MapToListOfEntriesExample {
public static void main(String[] args) {
Map<Integer, String> userMap = new HashMap<>();
userMap.put(1, "Alice");
userMap.put(2, "Bob");
userMap.put(3, "Charlie");
// 转换为 List<Map.Entry<K, V>>
List<Map.Entry<Integer, String>> entryList = new ArrayList<>(userMap.entrySet());
// 使用 Stream API
List<Map.Entry<Integer, String>> entryListStream = userMap.entrySet().stream()
.collect(Collectors.toList());
System.out.println(entryList);
// 输出: [1=Alice, 2=Bob, 3=Charlie] (注意:Map.Entry 的打印格式)
}
}
Map 转换为 List<K>(只获取键)
import java.util.*;
import java.util.stream.Collectors;
public class MapToListOfKeysExample {
public static void main(String[] args) {
Map<Integer, String> userMap = new HashMap<>();
userMap.put(1, "Alice");
userMap.put(2, "Bob");
userMap.put(3, "Charlie");
// 转换为 List<K>
List<Integer> keyList = new ArrayList<>(userMap.keySet());
// 使用 Stream API
List<Integer> keyListStream = userMap.keySet().stream()
.collect(Collectors.toList());
System.out.println(keyList);
// 输出: [1, 2, 3] (顺序不确定)
}
}
Map 转换为 List<V>(只获取值)
这是最常用的 Map 转 List 的场景之一。
import java.util.*;
import java.util.stream.Collectors;
public class MapToListOfValuesExample {
public static void main(String[] args) {
Map<Integer, String> userMap = new HashMap<>();
userMap.put(1, "Alice");
userMap.put(2, "Bob");
userMap.put(3, "Charlie");
// 转换为 List<V>
List<String> valueList = new ArrayList<>(userMap.values());
// 使用 Stream API (可以方便地进行后续处理,如排序)
List<String> valueListStream = userMap.values().stream()
.sorted() // 对值进行排序
.collect(Collectors.toList());
System.out.println("原始值列表: " + valueList);
System.out.println("排序后值列表: " + valueListStream);
// 输出 (顺序不确定):
// 原始值列表: [Alice, Bob, Charlie]
// 排序后值列表: [Alice, Bob, Charlie]
}
}
总结与最佳实践
| 转换方向 | 目标类型 | 核心方法/示例 | 备注 |
|---|---|---|---|
List -> Map |
Map<K, V> |
list.stream().collect(Collectors.toMap(...)) |
首选方法,简洁高效。 |
for (item : list) { map.put(...) } |
传统方法,代码冗长。 | ||
List -> Map |
处理键冲突 | Collectors.toMap(..., (oldVal, newVal) -> newVal) |
提供合并函数决定保留旧值还是新值。 |
List -> Map |
Map<K, List<V>> |
list.stream().collect(Collectors.groupingBy(...)) |
当多个元素可以有相同键时使用。 |
Map -> List |
List<Map.Entry<K,V>> |
new ArrayList<>(map.entrySet()) |
保留完整的键值对。 |
Map -> List |
List<K> |
new ArrayList<>(map.keySet()) |
获取所有键。 |
Map -> List |
List<V> |
new ArrayList<>(map.values()) |
最常用,获取所有值。 |
核心建议:
- 优先使用 Java 8 Stream API:对于任何集合转换,Stream API 都提供了更声明式、更易读、更强大的功能。
- 注意键的唯一性:在将
List转为Map时,一定要考虑键冲突的可能性,并提前决定好处理策略。 - 选择合适的
Map实现:如果需要保持插入顺序,可以使用LinkedHashMap;如果需要线程安全,可以使用ConcurrentHashMap,Stream API 的Collectors.toMap可以直接指定Map的实现:// 转换为保持插入顺序的 LinkedHashMap Map<Integer, User> orderedMap = users.stream() .collect(Collectors.toMap( User::getId, Function.identity(), (oldVal, newVal) -> oldVal, LinkedHashMap::new ));
