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")) { ... }