Java: Cómo convertir Lista a Mapa


224

Recientemente tuve una conversación con un colega sobre cuál sería la forma óptima de convertir Lista MapJava y si hay algún beneficio específico de hacerlo.

Quiero conocer el enfoque de conversión óptimo y realmente agradecería si alguien me puede guiar.

¿Es este un 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]);
}

2
¿Cuál es la mejor forma óptima? La optimización se realiza teniendo en cuenta ciertos parámetros (velocidad / memoria).
Daniel Fath

66
List difiere de Map en la forma conceptual: Map tiene la noción de par 'clave, valor', mientras que List no. Dado esto, no está claro cómo exactamente va a convertir de Lista a Mapa y viceversa.
Victor Sorokin el

1
@Daniel: Por Optimal, quise decir cuál es la mejor manera de hacerlo entre todas las diferentes formas entre las que no estoy seguro de todas, por lo que sería bueno ver algunas formas diferentes de convertir la lista en un mapa.
Rachel


Respuestas:


188
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 getKey()método que devuelve una clave del tipo adecuado.


1
También puede marcar la posición en la lista.
Jeremy el

@ Jim: ¿Necesito establecer el getKey()parámetro específico?
Rachel

Además, ¿cuál sería el valor en el mapa? ¿Puedes explicarlo con un ejemplo?
Rachel

1
@Rachel: el valor es el elemento de la lista y la clave es algo que lo hace único, lo cual usted determina. El uso de Jim getKey()fue arbitrario.
Jeremy el

1
Usted sabe el tamaño de antemano para que pueda hacer Map <Key, Item> map = new HashMap <Key, Item> (list.size ());
Víctor Romero

316

Con , podrás hacer esto en una línea usando streams y la Collectorsclase.

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

Demo breve:

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 señaló en los comentarios, puede usar en Function.identity()lugar de item -> item, aunque me parece i -> ibastante 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 Listy la función de mapeo que para un valor int, calcule el resultado del 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, recibirá un error que dice java.lang.IllegalStateException: Duplicate key 1 . Esto se debe a que 1% 3 es igual a 4% 3 y, por lo tanto, tiene el mismo valor de clave dada la función de asignación de teclas. En este caso, puede proporcionar un operador de fusión.

Aquí hay uno que suma los valores; (i1, i2) -> i1 + i2;eso 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 eso ayude! :)


19
mejor uso en Function.identity()lugar deitem -> item
Emmanuel Touzery

1
@ Emmanuel Touzery Bueno, Function.identity()vuelve t -> t;.
Alexis C.

2
Claro, ambos funcionan. Supongo que es cuestión de gustos. Encuentro Function.identity () más inmediatamente reconocible.
Emmanuel Touzery

OP no maneja ningún pojos, solo cadenas y números enteros a los que no puede llamar ::getKey.
phil294

@Blauhirn Lo sé, mi ejemplo se basa en la clase personalizada justo debajo. Usted es libre de usar cualquier función para generar sus claves a partir de los valores.
Alexis C.

115

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

17
Esto debería estar en la cima
Martin Andersson

66
"La guayaba contiene un superconjunto estrictamente compatible de la antigua y obsoleta Biblioteca de Colecciones de Google . Ya no deberías usar esa biblioteca". Es posible que se necesite una actualización.
Diminuto

3
El uso de una biblioteca externa para una operación tan simple es excesivo. Eso o un signo de una biblioteca estándar muy débil. En este caso, la respuesta de @ jim-garrison es perfectamente razonable. Es triste que Java no tenga métodos útiles como "mapa" y "reducir", pero no del todo necesario.
linuxdan

2
Esto usa guayaba. Desafortunadamente, Guava es muy lento en Android, por lo que esta solución no debe usarse en un proyecto de Android.
IgorGanapolsky

si el elemento en la lista tiene rolesNames duplicados, el código anterior arrojará una excepción,
Junchen Liu

17

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 uses.


16

Desde Java 8, la respuesta de @ZouZou usando el Collectors.toMaprecopilador es ciertamente 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 realmente se convierte en una 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);

Para una discusión de los parámetros de tipo de este método, vea mi pregunta de seguimiento .
glts el

Esto arrojará una excepción en caso de que haya claves duplicadas. Algo así como: Excepción en el hilo "main" java.lang.IllegalStateException: Duplicate key .... Para más detalles, consulte: codecramp.com/java-8-streams-api-convert-list-map
EMM

@EMM Por supuesto, según lo previsto y documentado en el Javadoc.
glts

Sí, actualicé la respuesta para cubrir el caso de duplicados. Por favor revise. Gracias
EMM

10

A Listy Mapson conceptualmente diferentes. A Listes una colección ordenada de artículos. Los elementos pueden contener duplicados, y un elemento puede no tener ningún concepto de un identificador único (clave). A Maptiene valores asignados a claves. Cada clave solo puede apuntar a un valor.

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


10

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

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

Entonces, en caso de que estemos interesados ​​en una implementación específica de la Mapinterfaz, por ejemplo HashMap, podemos usar el formulario sobrecargado 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 cualquiera Function.identity()o i->iestá bien, pero parece que en Function.identity()lugar de i -> ipodría ahorrar algo de memoria según esta respuesta relacionada .


1
¡El hecho curioso de que en 2019 una gran cantidad de personas aún no se dan cuenta de que no conocen la implementación real del Mapa que obtienen con lambda! En realidad, esta es solo una respuesta que encontré con Java 8 lambdas que usaría en la producción.
Pavlo

¿Hay alguna manera de recolectar especificando un tipo de Mapa pero sin usar una función de fusión?
Rosberg Linhares el


5

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

¿Como usar esto? ¿Qué debo pasar como converterparámetro en el método?
IgorGanapolsky

4

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

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

2

Se te ocurren muchas soluciones, 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 Google 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 lograr.

Como puede ver en los ejemplos, un Mapa es un mapeo de una clave a un valor, mientras que una lista es solo una serie de elementos que tienen una posición cada uno. Por lo tanto, simplemente no son convertibles automáticamente.


Pero podemos considerar la posición del elemento de lista como clave y poner su valor en el mapa, ¿es esta una buena solución?
Rachel

AFAIK sí! No hay ninguna función en el JDK que lo haga automáticamente, por lo que debe rodar la suya.
Daniel

¿Es posible hacer la última versión (usando el índice de matriz como clave de mapa) con flujos de Java 8?
phil294

2

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

Sientase 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

Puede aprovechar la API de secuencias 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/


1

Un ejemplo de Java 8 para convertir un List<?>objeto 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

como ya se dijo, en java-8 tenemos la solución concisa de Collectors:

  list.stream().collect(
         groupingBy(Item::getKey)
        )

y también, puede anidar varios grupos pasando un otro método groupingBy como segundo parámetro:

  list.stream().collect(
         groupingBy(Item::getKey, groupingBy(Item::getOtherKey))
        )

De esta manera, tendremos un mapa multinivel, como este: Map<key, Map<key, List<Item>>>


1

Usando flujos java-8

results.stream().collect(Collectors.toMap(e->((Integer)e[0]), e->(String)e[1]));

0

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 String 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

Apache Commons MapUtils.populateMap

Si no usa Java 8 y no desea usar un bucle explícito por alguna razón, intente MapUtils.populateMapdesde Apache Commons.

MapUtils.populateMap

Digamos que tiene 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 Pairclave del Pairobjeto.

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 forbucle es quizás más fácil de entender. (Esto a continuación da el mismo resultado):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.