Java: how to convert a List<?> to a Map<String,?>
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.
ebt
Updated on November 11, 2020Comments
-
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 almost 14 years+1 At times like these I wish there was a "favorite answer" option.
-
ebt almost 14 yearsperfect, this makes sense. Thanks
-
ebt almost 14 yearsI like the idea of using this vs reflection, Ill have to implement both methods. Thanks
-
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 almost 14 yearsThere's no sane reason to do it this way, just use a Function instead of reflection
-
ebt almost 14 yearsverbose and about as clear as mud :), would requiring a toString method be more pragmatic?
-
ColinD almost 14 yearsI'd recommend linking to Guava rather than Google Collections, as Guava has officially superseded it.
-
ColinD almost 14 yearsSince 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 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 meanObject.toString
onT
- that wouldn't be very useful. -
Brett almost 14 yearsI don't think the questioner wants
Object.toString
. Or at least the question seems to imply not wanting it. -
Jorn almost 14 years@Tom Then he only has to supply his own function instead of the prefab toString one
-
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 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 almost 14 yearsok, 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 almost 14 years@Tom What dependency do you mean?
-
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 almost 14 years@Jorn Dependency on Google Collections.
-
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 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 almost 14 yearsnever mind, idiotic question. toString is inherited from Object... palm -> face.
-
hovanessyan over 9 years+1 for avoid reflection
-
gilchris about 9 yearsGoogle Guava project is moved GitHub.
-
Alexis almost 8 yearsWell 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