杰瑞科技汇

Java Map与List如何相互转换?

核心概念

  • Map (映射): 存储键值对。Map<K, V> 中的 K 是键,V 是值,键是唯一的。
  • List (列表): 存储元素的有序集合。List<T> 中的 T 是列表中元素的类型。

转换的关键在于理解如何将一个结构中的元素映射到另一个结构中。


List 转换为 Map

这是最常见的转换场景之一,我们需要将 List 中的某个属性作为 Map 的键,另一个属性作为值。

List 中是普通对象,对象有唯一标识属性

假设我们有一个 User 类,我们想用 List<User> 创建一个 MapuserId 作为键,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 通常有以下几种情况:

  1. 转换为 List<Map.Entry<K, V>>:保留键值对。
  2. 转换为 List<K>:只保留键。
  3. 转换为 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>(只获取值)

这是最常用的 MapList 的场景之一。

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()) 最常用,获取所有值。

核心建议

  1. 优先使用 Java 8 Stream API:对于任何集合转换,Stream API 都提供了更声明式、更易读、更强大的功能。
  2. 注意键的唯一性:在将 List 转为 Map 时,一定要考虑键冲突的可能性,并提前决定好处理策略。
  3. 选择合适的 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
        ));
分享:
扫描分享到社交APP
上一篇
下一篇