【问题标题】:Java: how to convert a List<?> to a Map<String,?> [duplicate]Java:如何将 List<?> 转换为 Map<String,?> [重复]
【发布时间】:2011-03-13 02:17:39
【问题描述】:

我想找到一种方法来获取下面的对象特定例程并将其抽象为一个方法,您可以传递一个类、列表和字段名来获取一个 Map。 如果我能获得关于使用的模式的一般指针 or 等​​,这可以让我朝着正确的方向开始。

  Map<String,Role> mapped_roles = new HashMap<String,Role>();
    List<Role> p_roles = (List<Role>) c.list();
    for (Role el :  p_roles) {
        mapped_roles.put(el.getName(), el);
    }

到这个? (伪代码)

  Map<String,?> MapMe(Class clz, Collection list, String methodName)
  Map<String,?> map = new HashMap<String,?>();
    for (clz el :  list) {
        map.put(el.methodName(), el);
    }

有可能吗?

【问题讨论】:

  • @Aircule:在这里可以使用旧的仿函数 hack。
  • 我不明白。您想要一个x 事物的列表并将其转换为一个映射,其中键是事物名称,值是事物?
  • 对,key是传过来的方法返回的字符串值(对象Person有方法name(),人名成为key),value就是对象本身。比较字符串比手头有一个对象进行比较要容易得多。

标签: java generics list map


【解决方案1】:

使用Guava (formerly Google Collections)

Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, Functions.toStringFunction());

或者,如果您想提供自己的方法,使对象产生 String

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

【讨论】:

  • 我建议链接到 Guava 而不是 Google Collections,因为 Guava 已正式取代它。
  • 我不认为提问者想要Object.toString。或者至少这个问题似乎暗示着不想要它。
  • @Tom 那么他只需要提供自己的函数而不是预制的 toString one
  • @Tom 我觉得 Google Collections/Guava 是一个大多数 Java 项目都可以从中受益的库。特别是如果替代方案是从头开始并以更具限制性的方式(如您的答案)或做某事......不安全......如反射答案。
  • @Tom 答案的第一部分中已经存在该依赖项。 Colin 完美地解释了为什么拥有依赖关系而不是像你暗示的成本是一种资产
【解决方案2】:

Java 8 流和方法引用使这变得如此简单,您不需要辅助方法。

Map<String, Foo> map = listOfFoos.stream()
    .collect(Collectors.toMap(Foo::getName, Function.identity()));

如果可能有重复键,可以用toMap overload that takes a value merge function聚合值,也可以用groupingBy收集成一个列表:

//taken right from the Collectors javadoc
Map<Department, List<Employee>> byDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDepartment));

如上所示,这些都不是 String 特有的——您可以在任何类型上创建索引。

如果您有很多对象要处理和/或您的索引功能很昂贵,您可以使用Collection.parallelStream()stream().parallel() (they do the same thing) 并行处理。在这种情况下,您可以使用toConcurrentMapgroupingByConcurrent,因为它们允许流实现仅将元素放入 ConcurrentMap,而不是为每个线程制作单独的映射然后合并它们。

如果您不想在调用站点提交Foo::getName(或任何特定方法),您可以使用调用者传入的函数,存储在字段中等。实际创建函数的人仍然可以利用方法引用或 lambda 语法。

【讨论】:

    【解决方案3】:

    这就是我要做的。我不完全确定我是否正确处理了泛型,但是哦,好吧:

    public <T> Map<String, T> mapMe(Collection<T> list) {
       Map<String, T> map = new HashMap<String, T>();
       for (T el : list) {
           map.put(el.toString(), el);
       }   
       return map;
    }
    

    只需将 Collection 传递给它,并让您的类实现 toString() 以返回名称。多态会处理它。

    【讨论】:

    • 我喜欢使用这个与反射的想法,我必须实现这两种方法。谢谢
    • 由于这只适用于toString()作为获取索引键的函数,它不是一个很好的通用解决方案,显然不适用于对象的任意属性。反射解决方案更糟糕,并且在重构时没有编译器错误。
    • 那么在这种情况下,您可以使用 public static Map。使用 BaseModel 包含例如字段 id 和 getId() ...显然,这仅在您尝试转换为 map 的对象集是 BaseModel 类型时才有效
    【解决方案4】:

    如果您确定 List 中的每个对象都有唯一索引,请使用 Guava 和 Jorn 建议的 Maps.uniqueIndex

    另一方面,如果多个对象的索引字段可能具有相同的值(虽然对于您的具体示例可能不正确,但在许多用例中对于此类事情来说是正确的),则更多执行此索引的一般方法是使用Multimaps.index(Iterable<V> values, Function<? super V,K> keyFunction) 创建一个ImmutableListMultimap&lt;K,V&gt;,将每个键映射到一个或多个匹配值。

    下面是一个使用自定义 Function 的示例,它在对象的特定属性上创建索引:

    List<Foo> foos = ...
    ImmutableListMultimap<String, Foo> index = Multimaps.index(foos,
        new Function<Foo, String>() {
          public String apply(Foo input) {
            return input.getBar();
          }
        });
    
    // iterate over all Foos that have "baz" as their Bar property
    for (Foo foo : index.get("baz")) { ... }
    

    【讨论】:

      【解决方案5】:

      避免像瘟疫一样的反思。

      不幸的是,Java 的语法很冗长。 (最近的 JDK7 提案会使其更加简洁。)

      interface ToString<T> {
          String toString(T obj);
      }
      
      public static <T> Map<String,T> stringIndexOf(
          Iterable<T> things,
          ToString<T> toString
      ) {
          Map<String,T> map = new HashMap<String,T>();
          for (T thing : things) {
              map.put(toString.toString(thing), thing);
          }
          return map;
      }
      

      目前调用为:

      Map<String,Thing> map = stringIndexOf(
          things,
          new ToString<Thing>() { public String toString(Thing thing) {
              return thing.getSomething();
          }
      );
      

      在JDK7中,可能是这样的:

      Map<String,Thing> map = stringIndexOf(
          things,
          { thing -> thing.getSomething(); }
      );
      

      (可能需要yield。)

      【讨论】:

      • 冗长而且几乎像泥巴一样清晰:),需要一个 toString 方法会更实用吗?
      • @ebt 我认为清晰度没有任何问题。名字可以更好地选择。 / 我不明白你对toString 方法的评论。你的意思是Object.toString 上的T - 这不会很有用。
      • 好的,我花了几次迭代才把它拼凑起来。实现 toString 要求您的输入对象实现 toString 而不是假设 toString 方法存在并在运行时抛出错误(尽管您可以在方法上添加 throws 对吗?)
      • 没关系,愚蠢的问题。 toString 继承自 Object... palm -> face。
      • +1 避免反射
      【解决方案6】:

      使用反射和泛型:

      public static <T> Map<String, T> MapMe(Class<T> clz, Collection<T> list, String methodName)
      throws Exception{
        Map<String, T> map = new HashMap<String, T>();
        Method method = clz.getMethod(methodName);
        for (T el : list){
          map.put((String)method.invoke(el), el);
        }
        return map;
      }
      

      在您的文档中,请确保您提到方法的返回类型必须是字符串。否则,它会在尝试强制转换返回值时抛出 ClassCastException。

      【讨论】:

      • +1 在这样的时候,我希望有一个“最喜欢的答案”选项。
      • 完美,这是有道理的。谢谢
      • -1 - 这可能在美好的一天工作,但它很脆弱......而且可能比原始代码贵很多。
      • 没有理智的理由这样做,只需使用函数而不是反射
      • @mangst - 如果程序员传递了错误的名称或方法的签名更改或列表签名更改,它会中断(没有编译错误)...
      猜你喜欢
      • 2020-03-17
      • 1970-01-01
      • 2021-04-25
      • 2023-03-26
      • 2018-09-06
      • 1970-01-01
      • 2021-02-23
      • 1970-01-01
      相关资源
      最近更新 更多