如何将List转换为Map?

293

最近我和一位同事讨论了在Java中将List转换为Map的最佳方式以及这样做是否有特定的好处。

我想知道最佳的转换方法,并且非常感谢任何人能够指导我。

这种方法可行吗:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}

2
什么是最佳的优化方式?优化是根据特定参数(速度/内存)进行的。 - Daniel Fath
7
列表和映射在概念上有所不同--映射具有“键,值”对的概念,而列表则没有。鉴于此,如何从列表转换为映射以及如何从映射转回列表并不清楚。 - Victor Sorokin
1
@Daniel:所谓最优,是指在所有不同的方法中,哪种方法是最好的,因为我不确定所有的方法,所以看到一些不同的将列表转换为映射的方法会很好。 - Rachel
可能是重复的问题:Java:如何将List<?>转换为Map<String,?> - ripper234
Java 8 HashMap而不是任何映射实现:https://dev59.com/N2855IYBdhLWcg3wy3sc#47736750 - akhil_mittal
20个回答

452

使用,你可以使用流(streams)Collectors类把这个操作化为一行代码。

Map<String, Item> map = 
    list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

简短演示:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test{
    public static void main (String [] args){
        List<Item> list = IntStream.rangeClosed(1, 4)
                                   .mapToObj(Item::new)
                                   .collect(Collectors.toList()); //[Item [i=1], Item [i=2], Item [i=3], Item [i=4]]

        Map<String, Item> map = 
            list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

        map.forEach((k, v) -> System.out.println(k + " => " + v));
    }
}
class Item {

    private final int i;

    public Item(int i){
        this.i = i;
    }

    public String getKey(){
        return "Key-"+i;
    }

    @Override
    public String toString() {
        return "Item [i=" + i + "]";
    }
}

输出:

Key-1 => Item [i=1]
Key-2 => Item [i=2]
Key-3 => Item [i=3]
Key-4 => Item [i=4]

正如评论中所指出的那样,您可以使用 Function.identity() 代替 item -> item,但我发现 i -> i 更加明确。

而且要完整地注意,如果您的函数不是双射的,可以使用二元运算符。例如,让我们考虑这个 List 和映射函数,对于一个 int 值,计算它模 3 的结果:

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), i -> i));
当运行此代码时,您将收到一个错误,指出 java.lang.IllegalStateException: Duplicate key 1。这是因为 1 % 3 与 4 % 3 相同,因此在键映射函数中给定相同的键值。在这种情况下,您可以提供合并操作符。
这里有一个求和值的方法可用于替换 (i1, i2) -> i1 + i2;,即方法引用 Integer::sum
Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), 
                                   i -> i, 
                                   Integer::sum));

现在的输出结果为:

0 => 9 (i.e 3 + 6)
1 => 5 (i.e 1 + 4)
2 => 7 (i.e 2 + 5)

27
使用Function.identity()代替item -> item更为优秀。 - Emmanuel Touzery
2
@EmmanuelTouzery 好的,Function.identity() 返回 t -> t; - Alexis C.
3
可以,两种方法都可以。我认为这是个人口味问题。我发现Function.identity()更容易被立刻识别。 - Emmanuel Touzery
使用流会导致性能下降,除非您正在处理非常大的数据集-在此处阅读评论:https://dev59.com/vmEh5IYBdhLWcg3wNxPM在这里,您可以找到更一般的文章:https://jaxenter.com/java-performance-tutorial-how-fast-are-the-java-8-streams-118830.html - Viktor Yarmak
不错,@AlexisC。这对我非常有帮助 :) - George Fandango
显示剩余3条评论

220
List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>(list.size());
for (Item i : list) map.put(i.getKey(),i);

假设每个项目都有一个返回正确类型键的getKey()方法。

1
你也可以根据列表中的位置进行键入。 - Jeremy
还有,Map 中的值是什么?你能举个例子详细说明吗? - Rachel
2
@Rachel -- 值是列表中的项,而键是使该项唯一的东西,由您确定。Jim对getKey()的使用是任意的。 - Jeremy
1
你已经知道大小,所以可以这样做:Map<Key,Item> map = new HashMap<Key,Item>(list.size()); - Víctor Romero
干净而充足,不需要为每个处理的项编写回调函数。迄今为止最好的响应,不管可用什么花哨的流或实用函数。 - John Fantastico
显示剩余5条评论

113

万一这个问题没有被关闭为重复,正确的答案是使用Google Collections

Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
  public String apply(Role from) {
    return from.getName(); // or something else
  }});

7
Guava 包含了一个严格兼容旧版 Google Collections Library 的超集。您不应再使用该库。 可能需要更新。 - Tiny
4
使用外部库来进行如此简单的操作有些杀鸡焉用牛刀,或许这也暴露了标准库的不足。在这种情况下,@jim-garrison的答案是完全合理的。令人遗憾的是,Java没有"map"和"reduce"这样有用的方法,但不是完全必要的。 - linuxdan
2
这里使用了Guava。不幸的是,Guava在Android上非常缓慢,因此这个解决方案不应该在Android项目中使用。 - IgorGanapolsky
如果列表中的项具有重复的roleNames,则上述代码将抛出异常。 - Junchen Liu
1
这将返回一个ImmutableMap。是否有另一种方法可以返回普通的Mutable Map? - GP cyborg

44

简短明了。

使用Java 8,您可以执行以下操作:

Map<Key, Value> result= results
                       .stream()
                       .collect(Collectors.toMap(Value::getName,Function.identity()));

Value 可以是您使用的任何对象。


21

Alexis已经在 Java 8 中使用方法 toMap(keyMapper, valueMapper) 发布了一个答案。根据文档, 该方法的实现如下:

返回的Map类型、可变性、可序列化性或线程安全性都没有保证。

因此,如果我们对 Map 接口的特定实现(如 HashMap)感兴趣,则可以使用重载形式,如下所示:

Map<String, Item> map2 =
                itemList.stream().collect(Collectors.toMap(Item::getKey, //key for map
                        Function.identity(),    // value for map
                        (o,n) -> o,             // merge function in case of conflict with keys
                        HashMap::new));         // map factory - we want HashMap and not any Map implementation

虽然使用 Function.identity() 或者 i->i 都可以,但是根据这个相关的 答案,使用 Function.identity() 可能会节省一些内存。


1
有趣的事实是,2019年仍有大量人没有意识到他们使用lambda得到的不是真正的Map实现!实际上,这只是我在Java 8 lambdas中找到的一个答案,我会在生产中使用它。 - P_M
有没有一种方法可以通过指定 Map 类型来收集数据,但不使用合并函数? - Rosberg Linhares

18

自Java 8以来,使用Collectors.toMap集合器的@ZouZou的答案肯定是解决此问题的惯用方法。

由于这是一个非常常见的任务,我们可以将其制作成静态实用程序。

这样,解决方案就真正成为了一行代码。

/**
 * Returns a map where each entry is an item of {@code list} mapped by the
 * key produced by applying {@code mapper} to the item.
 *
 * @param list the list to map
 * @param mapper the function to produce the key from a list item
 * @return the resulting map
 * @throws IllegalStateException on duplicate key
 */
public static <K, T> Map<K, T> toMapBy(List<T> list,
        Function<? super T, ? extends K> mapper) {
    return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}

以下是如何在 List<Student> 上使用它:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);

有关此方法的类型参数的讨论,请参见我的后续问题 - glts
当然,这是在Javadoc中预期和记录的。 - glts
是的,已更新答案以涵盖重复情况。请查看。谢谢。 - EMM

10

ListMap在概念上是不同的。一个List是一个有序的项目集合,这些项目可以包含重复项,而且某个项目可能没有任何唯一标识符(键)的概念。一个Map将值映射到键。每个键只能指向一个值。

因此,根据您List的项目,可能会或可能不会将其转换为Map。您的List的项目是否没有重复项?每个项目是否都有唯一的键?如果是,则可以将它们放入Map中。


9

5

通用方法

public static <K, V> Map<K, V> listAsMap(Collection<V> sourceList, ListToMapConverter<K, V> converter) {
    Map<K, V> newMap = new HashMap<K, V>();
    for (V item : sourceList) {
        newMap.put( converter.getKey(item), item );
    }
    return newMap;
}

public static interface ListToMapConverter<K, V> {
    public K getKey(V item);
}

如何使用?在该方法中,我应该传递什么作为“converter”参数? - IgorGanapolsky

5

使用Java-8流

Map<Integer, String> map = results.stream().collect(Collectors.toMap(e -> ((Integer) e[0]), e -> (String) e[1]));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接