Java: Cómo convertir Lista a Mapa


Recientemente he conversado con un colega sobre cuál sería la forma óptima de convertir List a Map en Java y si hay algún beneficio específico de hacerlo.

Quiero saber el enfoque óptimo de conversión y realmente apreciaría si alguien puede guiarme.

Es este buen enfoque:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}
Author: maytham-ɯɐɥʇʎɐɯ, 2010-11-09

16 answers

List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>();
for (Item i : list) map.put(i.getKey(),i);

Suponiendo, por supuesto, que cada elemento tiene un método getKey() que devuelve una clave del tipo apropiado.

 151
Author: Jim Garrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-04 19:08:31

Con java-8, usted será capaz de hacer esto en línea mediante arroyos, y el Collectors clase.

Map<String, Item> map = 
    list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

Demo corta:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test{
    public static void main (String [] args){
        List<Item> list = IntStream.rangeClosed(1, 4)
                                   .mapToObj(Item::new)
                                   .collect(Collectors.toList()); //[Item [i=1], Item [i=2], Item [i=3], Item [i=4]]

        Map<String, Item> map = 
            list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

        map.forEach((k, v) -> System.out.println(k + " => " + v));
    }
}
class Item {

    private final int i;

    public Item(int i){
        this.i = i;
    }

    public String getKey(){
        return "Key-"+i;
    }

    @Override
    public String toString() {
        return "Item [i=" + i + "]";
    }
}

Salida:

Key-1 => Item [i=1]
Key-2 => Item [i=2]
Key-3 => Item [i=3]
Key-4 => Item [i=4]

Como se indica en los comentarios, puede usar Function.identity() en lugar de item -> item, aunque encuentro i -> i bastante explícito.

Y para ser completo tenga en cuenta que puede usar un operador binario si su función no es biyectiva. Por ejemplo, consideremos esto List y la función de asignación que para un int valor, calcular el resultado de la misma módulo 3:

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), i -> i));

Al ejecutar este código, obtendrá un error que dice java.lang.IllegalStateException: Duplicate key 1. Esto se debe a que 1% 3 es lo mismo que 4% 3 y, por lo tanto, tiene el mismo valor de clave dada la función de asignación de claves. En este caso, puede proporcionar un operador de fusión.

Aquí hay uno que suma los valores; (i1, i2) -> i1 + i2; que se puede reemplazar con la referencia del método Integer::sum.

Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), 
                                   i -> i, 
                                   Integer::sum));

Que ahora produce:

0 => 9 (i.e 3 + 6)
1 => 5 (i.e 1 + 4)
2 => 7 (i.e 2 + 5)

Espero que ayude! :)

 210
Author: Alexis C.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-12 09:43:01

En caso de que esta pregunta no se cierre como un duplicado, la respuesta correcta es usar Google Collections :

Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
  public String apply(Role from) {
    return from.getName(); // or something else
  }});
 110
Author: ripper234,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:41

Desde Java 8, la respuesta de @ZouZou usando el recopilador Collectors.toMap es sin duda la forma idiomática de resolver este problema.

Y como esta es una tarea tan común, podemos convertirla en una utilidad estática.

De esa manera la solución se convierte realmente en una sola línea.

/**
 * Returns a map where each entry is an item of {@code list} mapped by the
 * key produced by applying {@code mapper} to the item.
 *
 * @param list the list to map
 * @param mapper the function to produce the key from a list item
 * @return the resulting map
 * @throws IllegalStateException on duplicate key
 */
public static <K, T> Map<K, T> toMapBy(List<T> list,
        Function<? super T, ? extends K> mapper) {
    return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}

Y así es como lo usarías en un List<Student>:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);
 13
Author: glts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:36

A List y Map son conceptualmente diferentes. Un List es una colección ordenada de elementos. Los elementos pueden contener duplicados, y un elemento puede no tener ningún concepto de identificador único (clave). A Map tiene valores asignados a claves. Cada clave solo puede apuntar a un valor.

Por lo tanto, dependiendo de los elementos de su List, puede o no ser posible convertirlo a un Map. ¿Los elementos de su List no tienen duplicados? Cada elemento tiene una clave única? Si es así, entonces es posible ponerlos en a Map.

 9
Author: Steve Kuo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-31 17:10:42

También hay una forma sencilla de hacer esto usando Mapas.uniqueIndex(...) de Google guava bibliotecas

 8
Author: Andrejs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-02 22:17:31

Método universal

public static <K, V> Map<K, V> listAsMap(Collection<V> sourceList, ListToMapConverter<K, V> converter) {
    Map<K, V> newMap = new HashMap<K, V>();
    for (V item : sourceList) {
        newMap.put( converter.getKey(item), item );
    }
    return newMap;
}

public static interface ListToMapConverter<K, V> {
    public K getKey(V item);
}
 5
Author: xxf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-23 23:10:48

Usando Java 8 puedes hacer lo siguiente:

Map<Key, Value> result= results
                       .stream()
                       .collect(Collectors.toMap(Value::getName,Function.identity()));

Value puede ser cualquier objeto que utilice.

 4
Author: stackFan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-01 19:56:50

Sin java-8, podrás hacer esto en colecciones Commons de una línea, y la clase Closure

List<Item> list;
@SuppressWarnings("unchecked")
Map<Key, Item> map  = new HashMap<Key, Item>>(){{
    CollectionUtils.forAllDo(list, new Closure() {
        @Override
        public void execute(Object input) {
            Item item = (Item) input;
            put(i.getKey(), item);
        }
    });
}};
 3
Author: Vitaliy Oliynyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-15 15:00:25

Alexis ya ha publicado una respuesta en Java 8 usando el método toMap(keyMapper, valueMapper). Según doc para la implementación de este método:

No hay garantías sobre el tipo, mutabilidad, serialización, o hilo-seguridad del Mapa devuelto.

Así que en caso de que estemos interesados en una implementación específica de la interfaz Map, por ejemplo, HashMap, entonces podemos usar la forma sobrecargada como:

Map<String, Item> map2 =
                itemList.stream().collect(Collectors.toMap(Item::getKey, //key for map
                        Function.identity(),    // value for map
                        (o,n) -> o,             // merge function in case of conflict with keys
                        HashMap::new));         // map factory - we want HashMap and not any Map implementation

Aunque usar Function.identity() o i->i está bien, pero parece Function.identity() en lugar de i -> ipodría ahorrar algo de memoria según esta respuesta relacionada.

 3
Author: i_am_zero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-10 07:22:17

Muchas soluciones vienen a la mente, dependiendo de lo que quieras lograr:

Cada elemento de la lista es clave y valor

for( Object o : list ) {
    map.put(o,o);
}

Los elementos de la lista tienen algo para buscarlos, tal vez un nombre:

for( MyObject o : list ) {
    map.put(o.name,o);
}

Los elementos de la lista tienen algo para buscarlos, y no hay garantía de que sean únicos: Use Googles MultiMaps

for( MyObject o : list ) {
    multimap.put(o.name,o);
}

Dando a todos los elementos la posición como clave:

for( int i=0; i<list.size; i++ ) {
    map.put(i,list.get(i));
}

...

Realmente depende de lo que quieras achive.

Como puede ver en los ejemplos, un mapa es una asignación de una clave a un valor, mientras que una lista es solo una serie de elementos que tienen una posición cada uno. Así que simplemente no son convertibles automáticamente.

 2
Author: Daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-10 06:17:13

Aquí hay un pequeño método que escribí exactamente para este propósito. Utiliza Validate de Apache Commons.

Siéntase libre de usarlo.

/**
 * Converts a <code>List</code> to a map. One of the methods of the list is called to retrive
 * the value of the key to be used and the object itself from the list entry is used as the
 * objct. An empty <code>Map</code> is returned upon null input.
 * Reflection is used to retrieve the key from the object instance and method name passed in.
 *
 * @param <K> The type of the key to be used in the map
 * @param <V> The type of value to be used in the map and the type of the elements in the
 *            collection
 * @param coll The collection to be converted.
 * @param keyType The class of key
 * @param valueType The class of the value
 * @param keyMethodName The method name to call on each instance in the collection to retrieve
 *            the key
 * @return A map of key to value instances
 * @throws IllegalArgumentException if any of the other paremeters are invalid.
 */
public static <K, V> Map<K, V> asMap(final java.util.Collection<V> coll,
        final Class<K> keyType,
        final Class<V> valueType,
        final String keyMethodName) {

    final HashMap<K, V> map = new HashMap<K, V>();
    Method method = null;

    if (isEmpty(coll)) return map;
    notNull(keyType, Messages.getString(KEY_TYPE_NOT_NULL));
    notNull(valueType, Messages.getString(VALUE_TYPE_NOT_NULL));
    notEmpty(keyMethodName, Messages.getString(KEY_METHOD_NAME_NOT_NULL));

    try {
        // return the Method to invoke to get the key for the map
        method = valueType.getMethod(keyMethodName);
    }
    catch (final NoSuchMethodException e) {
        final String message =
            String.format(
                    Messages.getString(METHOD_NOT_FOUND),
                    keyMethodName,
                    valueType);
        e.fillInStackTrace();
        logger.error(message, e);
        throw new IllegalArgumentException(message, e);
    }
    try {
        for (final V value : coll) {

            Object object;
            object = method.invoke(value);
            @SuppressWarnings("unchecked")
            final K key = (K) object;
            map.put(key, value);
        }
    }
    catch (final Exception e) {
        final String message =
            String.format(
                    Messages.getString(METHOD_CALL_FAILED),
                    method,
                    valueType);
        e.fillInStackTrace();
        logger.error(message, e);
        throw new IllegalArgumentException(message, e);
    }
    return map;
}
 2
Author: Kango_V,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-31 17:11:21

Puede aprovechar la API de flujos de Java 8.

public class ListToMap {

  public static void main(String[] args) {
    List<User> items = Arrays.asList(new User("One"), new User("Two"), new User("Three"));

    Map<String, User> map = createHashMap(items);
    for(String key : map.keySet()) {
      System.out.println(key +" : "+map.get(key));
    }
  }

  public static Map<String, User> createHashMap(List<User> items) {
    Map<String, User> map = items.stream().collect(Collectors.toMap(User::getId, Function.identity()));
    return map;
  }
}

Para más detalles visite: http://codecramp.com/java-8-streams-api-convert-list-map /

 2
Author: EMM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-09 04:33:48

Un ejemplo de Java 8 para convertir un List<?> de objetos en un Map<k, v>:

List<Hosting> list = new ArrayList<>();
list.add(new Hosting(1, "liquidweb.com", new Date()));
list.add(new Hosting(2, "linode.com", new Date()));
list.add(new Hosting(3, "digitalocean.com", new Date()));

//example 1
Map<Integer, String> result1 = list.stream().collect(
    Collectors.toMap(Hosting::getId, Hosting::getName));

System.out.println("Result 1 : " + result1);

//example 2
Map<Integer, String> result2 = list.stream().collect(
    Collectors.toMap(x -> x.getId(), x -> x.getName()));

Código copiado de:
https://www.mkyong.com/java8/java-8-convert-list-to-map /

 1
Author: Doss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-26 00:42:03

Me gusta la respuesta de Kango_V, pero creo que es demasiado compleja. Creo que esto es más simple, tal vez demasiado simple. Si está inclinado, puede reemplazar la cadena con un marcador genérico y hacer que funcione para cualquier tipo de clave.

public static <E> Map<String, E> convertListToMap(Collection<E> sourceList, ListToMapConverterInterface<E> converterInterface) {
    Map<String, E> newMap = new HashMap<String, E>();
    for( E item : sourceList ) {
        newMap.put( converterInterface.getKeyForItem( item ), item );
    }
    return newMap;
}

public interface ListToMapConverterInterface<E> {
    public String getKeyForItem(E item);
}

Usado así:

        Map<String, PricingPlanAttribute> pricingPlanAttributeMap = convertListToMap( pricingPlanAttributeList,
                new ListToMapConverterInterface<PricingPlanAttribute>() {

                    @Override
                    public String getKeyForItem(PricingPlanAttribute item) {
                        return item.getFullName();
                    }
                } );
 0
Author: cs94njw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-20 15:42:39

Apache Commons MapUtils.populateMap

Si no usas Java 8 y no quieres usar un bucle explícito por alguna razón, prueba MapUtils.populateMap desde Apache Commons.

MapUtils.populateMap

Digamos que tienes una lista de Pairs.

List<ImmutablePair<String, String>> pairs = ImmutableList.of(
    new ImmutablePair<>("A", "aaa"),
    new ImmutablePair<>("B", "bbb")
);

Y ahora desea un Mapa de la clave Pair para el objeto Pair.

Map<String, Pair<String, String>> map = new HashMap<>();
MapUtils.populateMap(map, pairs, new Transformer<Pair<String, String>, String>() {

  @Override
  public String transform(Pair<String, String> input) {
    return input.getKey();
  }
});

System.out.println(map);

Da salida:

{A=(A,aaa), B=(B,bbb)}

Dicho esto, un bucle for es quizás más fácil de entender. (Esto a continuación da lo mismo salida):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
 0
Author: typoerrpr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-27 02:46:25