Java: как преобразовать список <?> В карту <String,?>
Я хотел бы найти способ принять описанную ниже конкретную процедуру и описать ее в методе, который вы можете передать классу, списку и имени поля, чтобы вернуть карту.
Если бы я мог получить общий указатель на используемый шаблон или, и т.д., Который мог бы начать меня в правильном направлении.
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);
}
Возможно ли это?
Ответы
Ответ 1
Вот что я буду делать. Я не совсем уверен, правильно ли я обрабатываю дженерики, но хорошо:
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;
}
Просто передайте ему коллекцию и попросите классы реализовать toString(), чтобы вернуть имя. Полиморфизм позаботится об этом.
Ответ 2
Использование Guava (ранее 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
}});
Ответ 3
Избегайте отражения, как чума.
К сожалению, синтаксис 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
.)
Ответ 4
Java 8 потоки и ссылки на методы делают это настолько легким, что вам не нужен вспомогательный метод для него.
Map<String, Foo> map = listOfFoos.stream()
.collect(Collectors.toMap(Foo::getName, Function.identity()));
Если могут быть дублированные ключи, вы можете агрегировать значения с помощью перегрузки toMap, которая принимает функцию слияния значений, или вы можете использовать groupingBy для сбора в список:
//taken right from the Collectors javadoc
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
Как показано выше, ничто из этого не относится к String - вы можете создать индекс для любого типа.
Если у вас много объектов для обработки и/или ваша функция индексирования стоит дорого, вы можете перейти параллельно, используя Collection.parallelStream()
или stream().parallel()
(они делают то же самое). В этом случае вы можете использовать toConcurrentMap или groupingByConcurrent, поскольку они позволяют реализации потока просто вставлять элементы в ConcurrentMap вместо создания отдельных карт для каждого потока, а затем их слияния.
Если вы не хотите фиксировать на Foo::getName
(или какой-либо конкретный метод) на сайте вызова, вы можете использовать функцию, переданную вызывающим абонентом, сохраненную в поле и т.д. Тот, кто фактически создает функцию могут по-прежнему использовать ссылку на метод или лямбда-синтаксис.
Ответ 5
Использование отражений и дженериков:
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, когда он попытается применить возвращаемое значение.
Ответ 6
Если вы уверены, что каждый объект в List
будет иметь уникальный индекс, используйте Guava с предложением Йорна Maps.uniqueIndex
.
Если, с другой стороны, более чем один объект может иметь одно и то же значение для поля индекса (что, хотя это неверно для вашего конкретного примера, возможно, верно во многих случаях использования для такого рода вещей), тем больше общий способ это индексирование заключается в использовании Multimaps.index(значения Iterable <V> , Function <? super V, K > keyFunction) для создания a ImmutableListMultimap<K,V>
, который отображает каждую клавишу в одно или несколько совпадающих значений.
Вот пример, который использует пользовательский 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")) { ... }