【问题标题】:Java 8 List<V> into Map<K, V>Java 8 List<V> 到 Map<K, V>
【发布时间】:2013-12-20 06:02:55
【问题描述】:

我想使用 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 的情况下完成此任务。

在番石榴中:

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();
        }
    });
}

以及带有 Java 8 lambda 的 Guava。

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

【问题讨论】:

    标签: java lambda java-8 java-stream


    【解决方案1】:
    String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
        List<String> list = Arrays.asList(array);
        Map<Integer, String> map = list.stream()
                .collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
                    System.out.println("Dublicate key" + x);
                    return x;
                },()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
        System.out.println(map);
    

    复制密钥 AA

    {12=ASDFASDFASDF, 7=EEDDDAD, 4=CCCC, 3=BBB, 2=AA}
    

    【讨论】:

    • 你想在这里做什么??你读过这个问题吗?
    【解决方案2】:

    目前还只存在于 cmets 中的另一种可能性:

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

    如果您想使用子对象的参数作为键,则很有用:

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

    【讨论】:

      【解决方案3】:

      作为guava 的替代品,可以使用kotlin-stdlib

      private Map<String, Choice> nameMap(List<Choice> choices) {
          return CollectionsKt.associateBy(choices, Choice::getName);
      }
      

      【讨论】:

        【解决方案4】:
        List<V> choices; // your list
        Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
        //assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.
        

        【讨论】:

          【解决方案5】:

          如果必须覆盖相同键名的每个新值:

          public Map < String, Choice > convertListToMap(List < Choice > choices) {
              return choices.stream()
                  .collect(Collectors.toMap(Choice::getName,
                      Function.identity(),
                      (oldValue, newValue) - > newValue));
          }
          

          如果必须将所有选项分组到一个名称列表中:

          public Map < String, Choice > convertListToMap(List < Choice > choices) {
              return choices.stream().collect(Collectors.groupingBy(Choice::getName));
          }
          

          【讨论】:

            【解决方案6】:

            使用getName()作为键,Choice本身作为映射的值:

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

            【讨论】:

            • 请写一些描述以便用户理解。
            • 这里没有更多细节真是太糟糕了,因为我最喜欢这个答案。
            • Collectors.toMap(Choice::getName,c-&gt;c)(短 2 个字符)
            • 等于choices.stream().collect(Collectors.toMap(choice -&gt; choice.getName(),choice -&gt; choice)); 第一个函数是key,第二个函数是value
            • 我知道看到和理解c -&gt; c 是多么容易,但Function.identity() 包含更多的语义信息。我通常使用静态导入,这样我就可以使用identity()
            【解决方案7】:

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

            示例对象:

            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));
            

            【讨论】:

              【解决方案8】:

              列出的大多数答案,当列表中有重复项时会漏掉一个案例。在这种情况下,答案将抛出IllegalStateException。请参考以下代码处理重复列表

              public Map<String, Choice> convertListToMap(List<Choice> choices) {
                  return choices.stream()
                      .collect(Collectors.toMap(Choice::getName, choice -> choice,
                          (oldValue, newValue) -> newValue));
                }
              

              【讨论】:

                【解决方案9】:

                我将编写如何使用 泛型控制反转 将列表转换为映射。只是通用方法!

                也许我们有整数列表对象列表。所以问题如下:什么应该是的关键地图?

                创建界面

                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));
                    }
                

                例如,如果我们有 book 的对象,这个类就是为地图选择键

                public class BookKeyFinder implements KeyFinder<Long, Book> {
                    @Override
                    public Long getKey(Book e) {
                        return e.getPrice()
                    }
                }
                

                【讨论】:

                  【解决方案10】:

                  可以使用流来执行此操作。要消除显式使用Collectors 的需要,可以静态导入toMap(如Effective Java 第三版推荐的那样)。

                  import static java.util.stream.Collectors.toMap;
                  
                  private static Map<String, Choice> nameMap(List<Choice> choices) {
                      return choices.stream().collect(toMap(Choice::getName, it -> it));
                  }
                  

                  【讨论】:

                    【解决方案11】:

                    这可以通过两种方式完成。让 person 成为我们将用来演示它的类。

                    public class Person {
                    
                        private String name;
                        private int age;
                    
                        public String getAge() {
                            return age;
                        }
                    }
                    

                    persons 为要转换为地图的 Person 列表

                    1.在列表中使用简单的 foreach 和 Lambda 表达式

                    Map<Integer,List<Person>> mapPersons = new HashMap<>();
                    persons.forEach(p->mapPersons.put(p.getAge(),p));
                    

                    2.在给定列表上定义的 Stream 上使用收集器。

                     Map<Integer,List<Person>> mapPersons = 
                               persons.stream().collect(Collectors.groupingBy(Person::getAge));
                    

                    【讨论】:

                      【解决方案12】:
                      Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));
                      

                      甚至为我服务,

                      Map<String,Choice> map=  list1.stream().collect(()-> new HashMap<String,Choice>(), 
                                  (r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));
                      

                      【讨论】:

                        【解决方案13】:

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

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

                        【讨论】:

                        • 这不是一个好的选择,因为您对每个元素都执行 get() 调用,因此会增加操作的复杂性(如果 items 是哈希图,则为 o(n * k))。
                        • 在哈希图 O(1) 上不是 get(i) 吗?
                        • @IvovanderVeeken 代码 sn-p 中的 get(i) 在列表中,而不是在地图上。
                        • @Zaki 我说的是尼古拉斯的话。如果项目是哈希图而不是列表,我看不到 n*k 复杂度。
                        【解决方案14】:

                        这是StreamEx的解决方案

                        StreamEx.of(choices).toMap(Choice::getName, c -> c);
                        

                        【讨论】:

                          【解决方案15】:

                          如果您的键保证对于列表中的所有元素都是唯一的,则应将其转换为 Map&lt;String, List&lt;Choice&gt;&gt; 而不是 Map&lt;String, Choice&gt;

                          Map<String, List<Choice>> result =
                           choices.stream().collect(Collectors.groupingBy(Choice::getName));
                          

                          【讨论】:

                          • 这实际上为您提供了 Map> 处理非唯一键的可能性,但这不是 OP 所要求的。在 Guava 中,Multimaps.index(choices, Choice::getName) 可能是你想要的更好的选择。
                          • 或者更确切地说使用 Guava 的 Multimap 在相同键映射到多个值的情况下非常方便。 Guava 中有多种实用方法可以方便地使用此类数据结构,而不是创建 Map>
                          • @RichardNichols 为什么 guava Multimaps 方法是更好的选择?这可能会带来不便,因为它不会返回 Map 对象。
                          • @RichardNichols 这可能不是 OP 所要求的,但我正在寻找这个并且很高兴这个答案存在!
                          【解决方案16】:

                          简单的另一种选择

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

                          【讨论】:

                          • 使用 this 或 java 7 类型没有可行的区别。
                          • 我发现这比其他机制更容易阅读和理解发生了什么
                          • SO 使用 Java 8 Streams 询问。
                          【解决方案17】:

                          如果您不想使用 Collectors.toMap(),这里还有一个

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

                          【讨论】:

                          • 哪一个比Collectors.toMap()或我们自己的HashMap更好?
                          • 此示例提供了如何在地图中放置其他内容的示例。我想要一个方法调用没有提供的值。谢谢!
                          • 第三个参数函数不正确。在那里你应该提供一些函数来合并两个 Hashmap,比如 Hashmap::putAll
                          【解决方案18】:

                          如果您不介意使用第 3 方库,AOL 的 cyclops-react lib(披露我是贡献者)具有所有 JDK Collection 类型的扩展,包括 ListMap

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

                          【讨论】:

                            【解决方案19】:
                            Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                                            .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));
                            

                            【讨论】:

                              【解决方案20】:

                              我试图这样做并发现,使用上面的答案,当使用 Functions.identity() 作为 Map 的键时,由于输入问题,我在使用像 this::localMethodName 这样的本地方法来实际工作时遇到问题.

                              Functions.identity() 在这种情况下实际上对打字做了一些事情,所以该方法只能通过返回 Object 并接受 Object 的参数来工作

                              为了解决这个问题,我最终放弃了Functions.identity(),改用s-&gt;s

                              所以我的代码,在我的例子中列出了一个目录中的所有目录,并为每个目录使用目录的名称作为映射的键,然后使用目录名称调用一个方法并返回一个项目集合,看起来像:

                              Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
                              .map(File::getName)
                              .collect(Collectors.toMap(s->s, this::retrieveBrandItems));
                              

                              【讨论】:

                                【解决方案21】:

                                我使用这种语法

                                Map<Integer, List<Choice>> choiceMap = 
                                choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
                                

                                【讨论】:

                                • groupingBy 创建一个Map&lt;K,List&lt;V&gt;&gt;,而不是Map&lt;K,V&gt;
                                • ulises 回答的重复。而且,String getName();(不是整数)
                                【解决方案22】:

                                基于Collectors documentation,它很简单:

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

                                【讨论】:

                                • 附带说明,即使在 Java 8 之后,JDK 仍然无法参加简洁竞赛。 Guava 替代品看起来非常易读:Maps.uniqueIndex(choices, Choice::getName).
                                • 使用JOOL 库中的(静态导入的)Seq(我推荐给任何使用 Java 8 的人),您还可以通过以下方式提高简洁性:seq(choices).toMap(Choice::getName)
                                • 使用 Function.identity 有什么好处吗?我的意思是,它 -> 它更短
                                • @shabunc 我不知道有什么好处,实际上我自己使用it -&gt; it。此处使用Function.identity() 主要是因为它在参考文档中使用,这就是我在撰写本文时对 lambdas 的全部了解
                                • @zapl,哦,原来这背后是有原因的——stackoverflow.com/questions/28032827/…
                                猜你喜欢
                                • 2019-01-23
                                • 2020-04-08
                                • 2014-02-09
                                • 1970-01-01
                                • 2017-05-20
                                • 1970-01-01
                                • 1970-01-01
                                • 2019-10-10
                                • 2018-11-23
                                相关资源
                                最近更新 更多