【问题标题】:Shortcut for adding to List in a HashMap在 HashMap 中添加到 List 的快捷方式
【发布时间】:2011-03-02 10:53:45
【问题描述】:

我经常需要获取对象列表并根据对象中包含的值将它们分组到 Map 中。例如。按国家/地区列出用户和组。

我的代码通常如下所示:

Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
    if(usersByCountry.containsKey(user.getCountry())) {
        //Add to existing list
        usersByCountry.get(user.getCountry()).add(user);

    } else {
        //Create new list
        List<User> users = new ArrayList<User>(1);
        users.add(user);
        usersByCountry.put(user.getCountry(), users);
    }
}

但是我不禁认为这很尴尬,并且一些大师有更好的方法。到目前为止我能看到的最接近的是MultiMap from Google Collections

有没有标准的方法?

谢谢!

【问题讨论】:

  • 真的应该是Map&lt;String, Set&lt;User&gt;&gt;吗?答案对您选择构建或使用的内容有所不同。请注意,Google 集合提供了对各种类型列表和集合的嵌套集合的改进。
  • 为 .Net 和 Linq 删除 Java。
  • @Hamish:是的,因为我们对依赖关系的担忧完全无关紧要!
  • @Hamish:如果我这样做了,我再也无法面对我的 .Net 编码兄弟了。耻辱!

标签: java collections hashmap


【解决方案1】:

在 Java 8 中,您可以使用 Map#computeIfAbsent()

Map<String, List<User>> usersByCountry = new HashMap<>();

for (User user : listOfUsers) {
    usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user);
}

或者,利用 Stream API 的 Collectors#groupingBy() 直接从 List 转到 Map

Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry));

在 Java 7 或更低版本中,你能得到的最好的结果如下:

Map<String, List<User>> usersByCountry = new HashMap<>();

for (User user : listOfUsers) {
    List<User> users = usersByCountry.get(user.getCountry());
    if (users == null) {
        users = new ArrayList<>();
        usersByCountry.put(user.getCountry(), users);
    }
    users.add(user);
}

Commons Collections 有一个LazyMap,但它没有参数化。 Guava 没有 LazyMapLazyList,但您可以使用 Multimap,如 answer of polygenelubricants below 所示。

【讨论】:

  • 你可以进一步缩短它:usersByCountry.put(user.getCountry(), users = new ArrayList&lt;&gt;()); 虽然我相信有些人会对此皱眉。
  • 我知道没关系但也许新手需要知道映射函数会获取key作为参数,所以最好使用'k'而不是'v'@987654340 @
【解决方案2】:

Guava 的 Multimap 确实是最合适的数据结构,事实上,Multimaps.index(Iterable&lt;V&gt;, Function&lt;? super V,K&gt;) 实用方法可以完全满足您的需求:采用 Iterable&lt;V&gt;List&lt;V&gt; 是),并且应用Function&lt;? super V, K&gt; 来获取Multimap&lt;K,V&gt; 的密钥。

这是文档中的一个示例:

例如,

  List<String> badGuys
      = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
  Function<String, Integer> stringLengthFunction = ...;
  Multimap<Integer, String> index
      = Multimaps.index(badGuys, stringLengthFunction);
  System.out.println(index);

打印

 {4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}

在你的情况下,你会写一个Function&lt;User,String&gt; userCountryFunction = ...

【讨论】:

  • +1 让我感到沮丧的是,涉及编写比这更多代码的答案排名更高,只是因为它们是最快的。:(
  • @Kevin:我希望你最终能停下来 =) 顺便说一句,我计划最终在各种 Guava 类上写关于 stackoverflow 的 Q/A 文章来展示它的功能。
  • 我每天只来一次或两次,这样可以保证我永远没有机会得到我的答案。 我认为你的想法很棒。我假设您的意思是发布问题并自己回答。你会得到一些人告诉你这有什么不道德的,但它得到了更广泛的 SO 社区的明确认可,因为他们的目标是让 SO 拥有出色的内容。
  • +1 为例。我会对这些文章感到好奇。 Guava 库应该引起更多关注。
【解决方案3】:

当我必须处理一个集合值映射时,我几乎总是在类中编写一个小的 putIntoListMap() 静态实用程序方法。如果我发现自己在多个类中都需要它,我会将该方法放入实用程序类中。像这样的静态方法调用有点难看,但它们比每次都输入代码要干净得多。除非多地图在您的应用程序中扮演非常重要的角色,否则恕我直言,引入另一个依赖项可能不值得。

【讨论】:

    【解决方案4】:

    通过使用lambdaj,您只需一行代码即可获得该结果,如下所示:

    Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));
    

    Lambdaj 还提供了许多其他功能,以使用非常易读的领域特定语言来操作集合。

    【讨论】:

      【解决方案5】:

      我们似乎经常这样做,所以我创建了一个模板类

      public abstract class ListGroupBy<K, T> {
      public Map<K, List<T>> map(List<T> list) {
          Map<K, List<T> > map = new HashMap<K, List<T> >();
          for (T t : list) {
              K key = groupBy(t);
              List<T> innerList = map.containsKey(key) ? map.get(key) : new ArrayList<T>();
              innerList.add(t);
              map.put(key, innerList);
          }
          return map;
      }
      
      protected abstract K groupBy(T t);
      }
      

      您只需为 groupBy 提供 impl

      你的情况

      String groupBy(User u){return user.getCountry();}
      

      【讨论】:

        【解决方案6】:

        GC 库中的LinkedHashMultimap 似乎满足了您的确切需求。如果您可以忍受依赖项,那么您的所有代码都将变为:

        SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
        // .. other stuff, then whenever you need it:
        countryToUserMap.put(user.getCountry(), user);
        

        插入顺序得到维护(几乎所有它看起来就像你对列表所做的那样)并且重复被排除在外;您当然可以根据需要切换到基于散列的普通集或树集(或列表,尽管这似乎不是您所需要的)。如果您要求一个没有用户的国家/地区,每个人都有小马等,则会返回空集合 - 我的意思是,请查看 API。它会为你做很多事情,所以依赖可能是值得的。

        【讨论】:

        • +1 谢谢,很高兴知道,但 BalusC 的优化是我所追求的。
        【解决方案7】:
        Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
        for(User user : listOfUsers) {
            List<User> users = usersByCountry.get(user.getCountry());
            if (users == null) {        
                usersByCountry.put(user.getCountry(), users = new ArrayList<User>());
            }
            users.add(user);
        }
        

        【讨论】:

          【解决方案8】:

          添加元素的一种简洁易读的方法如下:

          String country = user.getCountry();
          Set<User> users
          if (users.containsKey(country))
          {
              users = usersByCountry.get(user.getCountry());
          }
          else
          {
              users = new HashSet<User>();
              usersByCountry.put(country, users);
          }
          users.add(user);
          

          请注意,调用containsKeyget 并不比只调用get 并测试null 的结果慢。

          【讨论】:

          • 本身的调用确实并不慢,但查找现在发生了两次而不是一次。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-07-14
          • 2019-06-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多