Java: how to convert a List<?> to a Map<String,?>

45,534

Solution 1

Here's what I would do. I am not entirely sure if I am handling generics right, but oh well:

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

Just pass a Collection to it, and have your classes implement toString() to return the name. Polymorphism will take care of it.

Solution 2

Using Guava (formerly Google Collections):

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

Or, if you want to supply your own method that makes a String out of the object:

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

Solution 3

Java 8 streams and method references make this so easy you don't need a helper method for it.

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

If there may be duplicate keys, you can aggregate the values with the toMap overload that takes a value merge function, or you can use groupingBy to collect into a list:

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

As shown above, none of this is specific to String -- you can create an index on any type.

If you have a lot of objects to process and/or your indexing function is expensive, you can go parallel by using Collection.parallelStream() or stream().parallel() (they do the same thing). In that case you might use toConcurrentMap or groupingByConcurrent, as they allow the stream implementation to just blast elements into a ConcurrentMap instead of making separate maps for each thread and then merging them.

If you don't want to commit to Foo::getName (or any specific method) at the call site, you can use a Function passed in by a caller, stored in a field, etc.. Whoever actually creates the Function can still take advantage of method reference or lambda syntax.

Solution 4

Avoid reflection like the plague.

Unfortunately, Java's syntax for this is verbose. (A recent JDK7 proposal would make it much more consise.)

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

Currently call as:

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

In JDK7, it may be something like:

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

(Might need a yield in there.)

Solution 5

Using reflection and generics:

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

In your documentation, make sure you mention that the return type of the method must be a String. Otherwise, it will throw a ClassCastException when it tries to cast the return value.

Share:
45,534
ebt
Author by

ebt

Updated on November 11, 2020

Comments

  • ebt
    ebt over 3 years

    I would like to find a way to take the object specific routine below and abstract it into a method that you can pass a class, list, and fieldname to get back a Map. If I could get a general pointer on the pattern used or , etc that could get me started in the right direction.

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

    to this? (Pseudo code)

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

    is it possible?

  • Amir Rachum
    Amir Rachum almost 14 years
    +1 At times like these I wish there was a "favorite answer" option.
  • ebt
    ebt almost 14 years
    perfect, this makes sense. Thanks
  • ebt
    ebt almost 14 years
    I like the idea of using this vs reflection, Ill have to implement both methods. Thanks
  • Stephen C
    Stephen C almost 14 years
    -1 - this might work on a good day, but it is fragile ... and probably a lot more expensive than the original code.
  • Jorn
    Jorn almost 14 years
    There's no sane reason to do it this way, just use a Function instead of reflection
  • ebt
    ebt almost 14 years
    verbose and about as clear as mud :), would requiring a toString method be more pragmatic?
  • ColinD
    ColinD almost 14 years
    I'd recommend linking to Guava rather than Google Collections, as Guava has officially superseded it.
  • ColinD
    ColinD almost 14 years
    Since this only works with toString() as the function for getting the index key, it's not a very good general solution and obviously doesn't work for any arbitrary property of an object. The reflection solution is even worse and breaks without compiler errors on refactoring.
  • Brett
    Brett almost 14 years
    @ebt I don't think there's anything wrong with the clarity. Well names could be better chosen. / I don't understand your comment about a toString method. Do you mean Object.toString on T - that wouldn't be very useful.
  • Brett
    Brett almost 14 years
    I don't think the questioner wants Object.toString. Or at least the question seems to imply not wanting it.
  • Jorn
    Jorn almost 14 years
    @Tom Then he only has to supply his own function instead of the prefab toString one
  • Brett
    Brett almost 14 years
    @Jorn Nice to see you've added a solution to your answer that answers the question! With a big dependency.
  • AI52487963
    AI52487963 almost 14 years
    @Stephen C: What do you mean it's "fragile"? It works perfectly, as long as the programmer passes in the right method name.
  • ebt
    ebt almost 14 years
    ok, took me several iterations to piece it together. Implementing toString requires that your input objects implement toString instead of assuming that the toString method exists and throws an error at runtime (although you could add throws on the method right?)
  • Jorn
    Jorn almost 14 years
    @Tom What dependency do you mean?
  • Stephen C
    Stephen C almost 14 years
    @mangst - it breaks (with no compilation errors) if the programmer passes the wrong name or the method's signature changes, or the list signature changes ...
  • Brett
    Brett almost 14 years
    @Jorn Dependency on Google Collections.
  • ColinD
    ColinD almost 14 years
    @Tom I feel like Google Collections/Guava is a library that most Java projects could benefit from. Especially if the alternative is reimplementing its functionality from scratch and in a more limiting fashion (like your answer) or doing something... unsafe... like the reflection answer.
  • Jorn
    Jorn almost 14 years
    @Tom That dependency was already there in the first part of the answer. And Colin explains perfectly why it's an asset to have the dependency instead of a cost like you're implying
  • ebt
    ebt almost 14 years
    never mind, idiotic question. toString is inherited from Object... palm -> face.
  • hovanessyan
    hovanessyan over 9 years
    +1 for avoid reflection
  • gilchris
    gilchris about 9 years
    Google Guava project is moved GitHub.
  • Alexis
    Alexis almost 8 years
    Well in that case you can use public static <T extends BaseModel> Map<String, T>. with BaseModel containing for example the field id and getId() ... Obviously this will only works if the Set of object you are trying to convert into map is of type BaseModel