将Java 8中的List<V>转换为Map<K, V>

1047

我想使用Java 8的流和lambda将对象列表转换为Map。

这是我在Java 7及以下版本中的写法。

private Map<String, Choice> nameMap(List<Choice> choices) {
        final Map<String, Choice> hashMap = new HashMap<>();
        for (final Choice choice : choices) {
            hashMap.put(choice.getName(), choice);
        }
        return hashMap;
}

我可以使用Java 8和Guava轻松实现这一点,但我想知道如何在不使用Guava的情况下完成。

在Guava中:

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, new Function<Choice, String>() {

        @Override
        public String apply(final Choice input) {
            return input.getName();
        }
    });
}

还有Java8 lambda的番石榴。

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}
23个回答

1509
根据Collectors文档,它就像这样简单:
Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));

176
顺便提一下,即使在Java 8之后,JDK仍无法在简洁性比赛中与Guava替代方案竞争。Guava替代方案看起来更易读:Maps.uniqueIndex(choices, Choice::getName) - Bogdan Calmac
4
使用从JOOL库中静态导入的Seq(我建议所有使用Java 8的人都使用),你也可以通过以下方式改善简洁性:seq(choices).toMap(Choice::getName) 该语句的作用是将choices序列转换为一个以Choice对象的名称为键的映射表。 - lukens
7
使用Function.identity是否有好处?我的意思是,它 -> 它更短。 - shabunc
10
@shabunc,我不知道有什么好处,而且我自己也使用 it -> it。在此使用 Function.identity() 主要是因为它在引用的文档中被使用,并且在编写时我对lambda表达式的了解只到这里。 - zapl
13
@zapl,事实上,这背后有原因 - https://dev59.com/q14c5IYBdhLWcg3wVpHw - shabunc
显示剩余7条评论

359
如果你的键不能保证在列表中所有元素中是唯一的,那么你应该将它转换成一个 Map<String, List<Choice>> 而不是一个 Map<String, Choice>
Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));

77
实际上,这会给你一个Map<String, List<Choice>>,处理了非唯一键的可能性,但不是OP所请求的。在Guava中,如果这正是你想要的,Multimaps.index(choices, Choice::getName)可能是更好的选择。 - Richard Nichols
或者使用Guava的Multimap<String, Choice>,在同一键映射到多个值的情况下非常方便。 Guava中有各种实用程序方法可供使用这些数据结构,而不是创建Map<String,List<Choice>>。 - Neeraj B.
1
@RichardNichols为什么Guava的Multimaps方法更好?尽管它不返回Map对象可能会有些不便。 - theyuv
2
@RichardNichols,这可能不是OP所要求的,但我正好在寻找这个,很高兴有这个答案存在! - ElectRocnic

180

getName()作为键(key),将Choice本身作为映射(map)的值(value):

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));

22
请提供一些说明,以便用户能够理解。 - Mayank Jain
4
很遗憾这里没有更多的细节,因为我最喜欢这个答案。 - MadConan
Collectors.toMap(Choice::getName,c -> c) - Ferrybig
9
等同于以下代码:choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice));第一个函数用作键,第二个函数用作值。 - waterscar
21
我知道c -> c很容易看懂,但是Function.identity()包含更多的语义信息。我通常使用静态导入,这样我就可以直接使用identity() - Hank D

36
大多数列出的答案都忽略了一个情况,即列表中有重复项。在这种情况下,他们的答案会抛出IllegalStateException异常。请参考下面的代码处理列表中的重复项
public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }

32

如果您不想使用Collectors.toMap(),这里有另一个选项。

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});

1
在使用Collectors.toMap()和上面示例中展示的自己的HashMap时,哪一个更好呢? - Swapnil Gangrade
1
这个示例提供了如何在地图中放置其他内容的示例。我想要一个方法调用未提供的值。谢谢! - th3morg
2
第三个参数函数不正确。在那里,您应该提供一些合并两个Hashmap的函数,类似于Hashmap :: putAll。 - jesantana

23

例如,如果您想将对象字段转换为映射:

示例对象:

class Item{
        private String code;
        private String name;

        public Item(String code, String name) {
            this.code = code;
            this.name = name;
        }

        //getters and setters
    }

使用与操作将列表转换为映射:

List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));

Map<String,String> map = list.stream()
     .collect(Collectors.toMap(Item::getCode, Item::getName));

23

另外一个简单的选项

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));

4
使用这个或者Java 7类型没有实质性的区别。 - Ashish Lohia
2
SO使用Java 8 Streams进行提问。 - Mehraj Malik

13

如果您不介意使用第三方库,AOL的cyclops-react库(声明:我是贡献者之一)具有所有JDK Collection类型的扩展,包括ListMap

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);

12

您可以使用IntStream创建索引流,然后将其转换为Map:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));

2
这不是一个好的选择,因为你对每个元素都进行了一次get()调用,从而增加了操作的复杂度(如果items是一个哈希映射,则为o(n * k))。 - Nicolas Nobelis
哈希表中的 get(i) 不是 O(1) 吗? - Ivo van der Veeken
@IvovanderVeeken,代码片段中的get(i)是针对列表而非映射表。 - Zaki
@Zaki,我在谈论Nicolas的评论。如果items是哈希表而不是列表,我就看不到n*k的复杂度了。 - Ivo van der Veeken

11

我将写出如何使用泛型控制反转将列表转换为映射的方法。只需要一个通用方法!

也许我们有整数列表对象列表。那么问题就是:映射的应该是什么?

创建接口

public interface KeyFinder<K, E> {
    K getKey(E e);
}

现在使用控制反转:

  static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
        return  list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
    }
例如,如果我们有书的对象,那么这个类是为地图选择键。
public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}

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