Java: come convertire l'elenco in mappa


224

Recentemente ho conversazione con un collega su quello che sarebbe il modo ottimale per convertire Lista Mapin Java e se ci sono vantaggi specifici di farlo.

Voglio conoscere un approccio di conversione ottimale e apprezzerei molto se qualcuno mi può guidare.

Questo buon approccio è:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}

2
Qual è il modo migliore ottimale? L'ottimizzazione viene effettuata tenendo conto di determinati parametri (velocità / memoria).
Daniel Fath,

6
Elenco differisce dalla mappa in modo concettuale - Mappa ha nozione di coppia "chiave, valore", mentre elenco no. Detto questo, non è chiaro come esattamente convertirai da Elenco a Mappa e viceversa.
Victor Sorokin,

1
@Daniel: Per Optimal, intendevo qual è il modo migliore per farlo in tutti i modi diversi tra non sono sicuro di tutti i modi e quindi sarebbe bello vedere alcuni modi diversi di convertire la lista in mappa.
Rachel,


Risposte:


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

Supponendo ovviamente che ogni articolo abbia un getKey()metodo che restituisce una chiave del tipo corretto.


1
È inoltre possibile digitare la posizione nell'elenco.
Jeremy,

@Jim: Devo impostare il getKey()parametro specifico?
Rachel,

Inoltre quale sarebbe il valore nella mappa, puoi elaborare con un esempio?
Rachel,

1
@Rachel: il valore è l'elemento nell'elenco e la chiave è qualcosa che rende unico l'elemento, che è determinato da te. L'uso di Jim getKey()era arbitrario.
Jeremy,

1
Conosci le dimensioni in anticipo in modo da poter fare Map <Key, Item> map = new HashMap <Key, Item> (list.size ());
Víctor Romero,

316

Con , sarai in grado di farlo in una riga usando gli stream e la Collectorsclasse.

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

Breve demo:

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 + "]";
    }
}

Produzione:

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

Come notato nei commenti, puoi usare Function.identity()invece di item -> item, anche se trovo i -> ipiuttosto esplicito.

E per essere completo nota che puoi usare un operatore binario se la tua funzione non è biiettiva. Consideriamo ad esempio questo Liste la funzione di mappatura che per un valore int calcoliamo il risultato di esso modulo 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));

Quando esegui questo codice, visualizzerai un errore java.lang.IllegalStateException: Duplicate key 1. Questo perché 1% 3 è uguale al 4% 3 e quindi ha lo stesso valore chiave dato la funzione di mappatura dei tasti. In questo caso è possibile fornire un operatore di unione.

Eccone uno che somma i valori; (i1, i2) -> i1 + i2;che può essere sostituito con il riferimento al metodo Integer::sum.

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

che ora genera:

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

Spero che sia d'aiuto! :)


19
meglio usare Function.identity()invece diitem -> item
Emmanuel Touzery il

1
@EmmanuelTouzery Bene, Function.identity()ritorna t -> t;.
Alexis C.,

2
Certo, entrambi funzionano. Immagino sia una questione di gusti. Trovo Function.identity () più immediatamente riconoscibile.
Emmanuel Touzery,

OP non gestisce alcun pojos, solo stringhe e numeri interi sui quali non è possibile eseguire il cal ::getKey.
phil294,

@Blauhirn Lo so, il mio esempio si basa sulla classe personalizzata qui sotto. Sei libero di usare qualunque funzione per generare le tue chiavi dai valori.
Alexis C.

115

Nel caso in cui questa domanda non venga chiusa come duplicata, la risposta giusta è utilizzare 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
Questo dovrebbe essere al top
Martin Andersson il

6
" Guava contiene un superset strettamente compatibile della vecchia e obsoleta libreria di raccolte Google . Non dovresti più utilizzare quella libreria. " Potrebbe essere necessario un aggiornamento.
Piccolo

3
L'uso di una libreria esterna per un'operazione così semplice è eccessivo. Quello o il segno di una libreria standard molto debole. In questo caso la risposta di @ jim-garrison è perfettamente ragionevole. È triste che java non abbia metodi utili come "mappa" e "riduzione" ma non del tutto necessari.
linuxdan,

2
Questo utilizza Guava. Sfortunatamente Guava è super lento su Android, quindi questa soluzione non dovrebbe essere utilizzata in un progetto Android.
IgorGanapolsky,

se l'elemento nell'elenco ha duplicati roleNames, il codice sopra genererà un'eccezione,
Junchen Liu

17

Utilizzando Java 8 puoi fare quanto segue:

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

Value può essere qualsiasi oggetto che usi.


16

Da Java 8, la risposta di @ZouZou utilizzando il Collectors.toMapraccoglitore è sicuramente il modo idiomatico per risolvere questo problema.

E poiché questo è un compito così comune, possiamo trasformarlo in un'utilità statica.

In questo modo la soluzione diventa davvero una soluzione.

/**
 * 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()));
}

Ed ecco come lo useresti su un List<Student>:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);

Per una discussione dei parametri di tipo di questo metodo, vedere la mia domanda di follow-up .
inizia il

Ciò genererà un'eccezione nel caso in cui siano presenti chiavi duplicate. Sth like: Eccezione nel thread "main" java.lang.IllegalStateException: chiave duplicata .... Per i dettagli vedere: codecramp.com/java-8-streams-api-convert-list-map
EMM

@EMM Naturalmente, come previsto e documentato nel Javadoc.
inizia il

Sì, aggiornata la risposta per coprire il caso dei duplicati. Per favore rivedere. Grazie
EMM l'

10

A Liste Mapsono concettualmente diversi. A Listè una raccolta ordinata di articoli. Gli articoli possono contenere duplicati e un articolo potrebbe non avere alcun concetto di identificatore univoco (chiave). A Mapha valori associati a chiavi. Ogni chiave può indicare solo un valore.

Pertanto, a seconda dei tuoi Listarticoli, potrebbe essere o meno possibile convertirlo in a Map. I tuoi Listarticoli non hanno duplicati? Ogni articolo ha una chiave univoca? In tal caso, è possibile inserirli in a Map.


10

Alexis ha già pubblicato una risposta in Java 8 usando il metodo toMap(keyMapper, valueMapper). Come da documento per l'implementazione di questo metodo:

Non ci sono garanzie sul tipo, la mutabilità, la serializzabilità o la sicurezza del thread della mappa restituita.

Quindi, nel caso in cui siamo interessati a un'implementazione specifica Mapdell'interfaccia, ad esempio HashMap, possiamo usare il modulo sovraccarico come:

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

Anche se l'utilizzo di Function.identity()o i->iva bene, ma sembra Function.identity()invece di i -> irisparmiare un po 'di memoria secondo questa risposta correlata .


1
Il fatto curioso che nel 2019 un'enorme quantità di persone non si renda ancora conto di non conoscere la reale implementazione della mappa che ottengono con lambda! In realtà questa è solo una risposta che ho trovato con Java 8 lambdas che userei in produzione.
Pavlo,

Esiste un modo per raccogliere specificando un tipo di mappa ma senza utilizzare una funzione di unione?
Rosberg Linhares,


5

Metodo universale

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

Come usare questo? Cosa devo passare come converterparametro nel metodo?
IgorGanapolsky,

4

Senza java-8, sarai in grado di farlo in una linea di raccolte Commons e nella classe 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);
        }
    });
}};

2

Vengono in mente molte soluzioni, a seconda di ciò che si desidera raggiungere:

Ogni elemento dell'elenco è chiave e valore

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

Gli elementi dell'elenco hanno qualcosa da cercare, forse un nome:

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

Gli elementi dell'elenco hanno qualcosa da cercare e non vi è alcuna garanzia che siano univoci: utilizzare Google MultiMaps

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

Dare a tutti gli elementi la posizione come chiave:

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

...

Dipende davvero da cosa vuoi raggiungere.

Come puoi vedere dagli esempi, una mappa è una mappatura da una chiave a un valore, mentre un elenco è solo una serie di elementi che hanno una posizione ciascuno. Quindi semplicemente non sono automaticamente convertibili.


Ma possiamo considerare la posizione dell'elemento elenco come chiave e metterne il valore nella mappa, è una buona soluzione?
Rachel,

AFAIK sì! Nel JDK non esiste alcuna funzione che lo fa automaticamente, quindi è necessario eseguire il rollback.
Daniel

è possibile eseguire l'ultima versione (utilizzando l'indice dell'array come chiave della mappa) con flussi Java 8?
phil294

2

Ecco un piccolo metodo che ho scritto proprio per questo scopo. Utilizza Convalida da Apache Commons.

Sentiti libero di 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

È possibile sfruttare l'API dei flussi di 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;
  }
}

Per maggiori dettagli visitare: http://codecramp.com/java-8-streams-api-convert-list-map/


1

Un esempio di Java 8 per convertire un List<?>oggetto in 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()));

Codice copiato da:
https://www.mkyong.com/java8/java-8-convert-list-to-map/


1

come già detto, in Java-8 abbiamo la soluzione concisa da Collezionisti:

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

e inoltre, puoi nidificare più gruppi passando un altro metodo groupingBy come secondo parametro:

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

In questo modo avremo una mappa multilivello, come questa: Map<key, Map<key, List<Item>>>


1

Utilizzo dei flussi java-8

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

0

Mi piace la risposta di Kango_V, ma penso che sia troppo complessa. Penso che sia più semplice, forse troppo semplice. Se inclinato, è possibile sostituire String con un marcatore generico e farlo funzionare per qualsiasi tipo di chiave.

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

Usato così:

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

                    @Override
                    public String getKeyForItem(PricingPlanAttribute item) {
                        return item.getFullName();
                    }
                } );

0

Mappa di Apache Commons MapUtils.populateMappa

Se non usi Java 8 e non vuoi usare un loop esplicito per qualche motivo, prova MapUtils.populateMapda Apache Commons.

MapUtils.populateMap

Supponi di avere un elenco di Pairs.

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

E ora vuoi una mappa della Pairchiave dell ' Pairoggetto.

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

dà output:

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

Detto questo, un forciclo è forse più facile da capire. (Questo sotto fornisce lo stesso risultato):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.