Java: Comment convertir une liste en carte

224

Récemment , j'ai conversation avec un collègue au sujet de ce qui serait la meilleure façon de convertir Listà MapJava et s'il y a des avantages spécifiques de le faire.

Je veux connaître l'approche de conversion optimale et j'apprécierais vraiment que quelqu'un puisse me guider.

Est-ce une bonne approche:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}
Rachel
la source
2
Quelle est la meilleure façon optimale? L'optimisation se fait en tenant compte de certains paramètres (vitesse / mémoire).
Daniel Fath
6
List diffère de Map sur le plan conceptuel - Map a la notion de paire «clé, valeur», contrairement à List. Compte tenu de cela, il est difficile de savoir exactement comment vous allez convertir la liste en carte et inversement.
Victor Sorokin
1
@Daniel: Par Optimal, je voulais dire quelle est la meilleure façon de le faire parmi toutes les différentes façons entre je ne suis pas sûr de toutes les façons et il serait donc bon de voir différentes façons de convertir la liste en carte.
Rachel

Réponses:

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

En supposant bien sûr que chaque élément possède une getKey()méthode qui renvoie une clé du type approprié.

Jim Garrison
la source
1
Vous pouvez également saisir le poste dans la liste.
Jeremy
@Jim: Dois-je définir le getKey()sur un paramètre spécifique?
Rachel
Quelle serait également la valeur de la carte, pouvez-vous élaborer avec un exemple?
Rachel
1
@Rachel - La valeur est l'élément dans la liste, et la clé est quelque chose qui rend l'élément unique, qui est déterminé par vous. L'utilisation de Jim getKey()était arbitraire.
Jeremy
1
Vous connaissez la taille à l'avance afin de pouvoir faire Map <Key, Item> map = new HashMap <Key, Item> (list.size ());
Víctor Romero
316

Avec , vous pourrez le faire sur une seule ligne en utilisant des flux et la Collectorsclasse.

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

Démo courte:

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

Production:

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

Comme indiqué dans les commentaires, vous pouvez utiliser à la Function.identity()place de item -> item, bien que je trouve i -> iplutôt explicite.

Et pour être complet, notez que vous pouvez utiliser un opérateur binaire si votre fonction n'est pas bijective. Par exemple, considérons ceci Listet la fonction de mappage que pour une valeur int, calculons le résultat de celle-ci 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));

Lors de l'exécution de ce code, vous obtiendrez une erreur indiquant java.lang.IllegalStateException: Duplicate key 1. En effet, 1% 3 est identique à 4% 3 et a donc la même valeur de clé compte tenu de la fonction de mappage de clé. Dans ce cas, vous pouvez fournir un opérateur de fusion.

En voici un qui additionne les valeurs; (i1, i2) -> i1 + i2;qui peut être remplacé par la référence de méthode Integer::sum.

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

qui génère désormais:

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

J'espère que ça aide! :)

Alexis C.
la source
19
mieux utiliser Function.identity()au lieu deitem -> item
Emmanuel Touzery
1
@EmmanuelTouzery Eh bien, Function.identity()revient t -> t;.
Alexis
2
Bien sûr, les deux fonctionnent. Je suppose que c'est une question de goût. Je trouve Function.identity () plus immédiatement reconnaissable.
Emmanuel Touzery
OP ne gère aucun pojos, juste des chaînes et des nombres entiers sur lesquels vous ne pouvez pas caler ::getKey.
phil294
@Blauhirn Je sais, mon exemple est basé sur la classe personnalisée juste en dessous. Vous êtes libre d'utiliser n'importe quelle fonction pour générer vos clés à partir des valeurs.
Alexis C.
115

Juste au cas où cette question ne serait pas fermée en double, la bonne réponse consiste à utiliser Google Collections :

Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
  public String apply(Role from) {
    return from.getName(); // or something else
  }});
ripper234
la source
17
Cela devrait être au sommet
Martin Andersson
6
" Guava contient un sur-ensemble strictement compatible de l'ancienne bibliothèque Google Collections obsolète . Vous ne devriez plus utiliser cette bibliothèque. " Une mise à jour pourrait être nécessaire.
Minuscule
3
L'utilisation d'une bibliothèque externe pour une opération aussi simple est exagérée. Cela ou le signe d'une bibliothèque standard très faible. Dans ce cas, la réponse de @ jim-garrison est parfaitement raisonnable. Il est triste que java n'ait pas de méthodes utiles comme "mapper" et "réduire" mais pas entièrement nécessaires.
linuxdan
2
Cela utilise la goyave. Malheureusement, Guava est super lent sur Android, donc cette solution ne doit pas être utilisée dans un projet Android.
IgorGanapolsky
si l'élément dans la liste a des noms de rôles en double, le code ci-dessus lève une exception,
Junchen Liu
17

En utilisant Java 8, vous pouvez faire ce qui suit:

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

Value peut être n'importe quel objet que vous utilisez.

stackFan
la source
16

Depuis Java 8, la réponse de @ZouZou en utilisant le Collectors.toMapcollecteur est certainement le moyen idiomatique de résoudre ce problème.

Et comme c'est une tâche si courante, nous pouvons en faire un utilitaire statique.

De cette façon, la solution devient vraiment une ligne.

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

Et voici comment l'utiliser sur List<Student>:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);
glts
la source
Pour une discussion sur les paramètres de type de cette méthode, voir ma question de suivi .
glts
Cela lèvera une exception au cas où il y aurait des clés en double. Sth like: Exception dans le thread "main" java.lang.IllegalStateException: Duplicate key .... Pour plus de détails, voir: codecramp.com/java-8-streams-api-convert-list-map
EMM
@EMM Bien sûr, comme prévu et documenté dans le Javadoc.
glts
Oui, mis à jour la réponse pour couvrir le cas des doublons. S'il-vous-plaît évaluez. Merci
EMM
10

A Listet Mapsont conceptuellement différents. A Listest une collection d'articles ordonnée. Les éléments peuvent contenir des doublons et un élément peut ne pas avoir de concept d'identifiant unique (clé). A Mapa des valeurs mappées sur des clés. Chaque clé ne peut pointer que vers une seule valeur.

Par conséquent, selon vos Listarticles, il peut ou non être possible de le convertir en a Map. Est-ce que vos Listarticles n'ont pas de doublons? Chaque article a-t-il une clé unique? Si oui, il est possible de les mettre dans un fichier Map.

Steve Kuo
la source
10

Alexis a déjà posté une réponse en Java 8 en utilisant la méthode toMap(keyMapper, valueMapper). Selon le doc pour cette implémentation de la méthode:

Il n'y a aucune garantie sur le type, la mutabilité, la sérialisation ou la sécurité des threads de la carte retournée.

Donc, dans le cas où nous sommes intéressés par une implémentation spécifique de l' Mapinterface, par exemple, HashMapnous pouvons utiliser le formulaire surchargé comme:

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

Bien que l'utilisation de soit Function.identity()ou i->isoit très bien, mais il semble Function.identity()au lieu de i -> ipeut économiser de la mémoire selon cette réponse associée .

akhil_mittal
la source
1
Le fait amusant qu'en 2019, un grand nombre de personnes ne réalisent toujours pas qu'elles ne connaissent pas la véritable mise en œuvre de la carte qu'elles obtiennent avec lambda! En fait, ce n'est qu'une réponse que j'ai trouvée avec les lambdas Java 8 que j'utiliserais en production.
Pavlo
Existe-t-il un moyen de collecter en spécifiant un type de carte mais sans utiliser de fonction de fusion?
Rosberg Linhares
8

Il existe également un moyen simple de le faire en utilisant Maps.uniqueIndex (...) de Google bibliothèques

Andrejs
la source
5

Méthode universelle

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);
}
xxf
la source
Comment l'utiliser? Que dois-je passer comme converterparamètre dans la méthode?
IgorGanapolsky
4

Sans java-8, vous pourrez le faire dans des collections Commons en une seule ligne et dans la 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);
        }
    });
}};
Vitaliy Oliynyk
la source
2

De nombreuses solutions viennent à l'esprit, selon ce que vous souhaitez réaliser:

Chaque élément de la liste est une clé et une valeur

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

Les éléments de liste ont quelque chose à rechercher, peut-être un nom:

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

Les éléments de liste ont quelque chose à rechercher, et il n'y a aucune garantie qu'ils soient uniques: utilisez Google Maps MultiMaps

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

Donner à tous les éléments la position comme clé:

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

...

Cela dépend vraiment de ce que vous voulez atteindre.

Comme vous pouvez le voir dans les exemples, une carte est un mappage d'une clé à une valeur, tandis qu'une liste n'est qu'une série d'éléments ayant chacun une position. Ils ne sont donc tout simplement pas convertibles automatiquement.

Daniel
la source
Mais nous pouvons considérer la position de l'élément de liste comme clé et mettre sa valeur sur la carte, est-ce une bonne solution?
Rachel
AFAIK oui! Il n'y a aucune fonction dans le JDK qui le fait automatiquement, vous devez donc lancer la vôtre.
Daniel
est-il possible de faire la dernière version (en utilisant l'index de tableau comme clé de carte) avec des flux java 8?
phil294
2

Voici une petite méthode que j'ai écrite exactement dans ce but. Il utilise Validate d'Apache Commons.

Sentez-vous libre de l'utiliser.

/**
 * 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;
}
Kango_V
la source
2

Vous pouvez tirer parti de l'API streams 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;
  }
}

Pour plus de détails, visitez: http://codecramp.com/java-8-streams-api-convert-list-map/

EMM
la source
1

Un exemple Java 8 pour convertir un List<?>des objets en 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()));

Code copié depuis:
https://www.mkyong.com/java8/java-8-convert-list-to-map/

Doss
la source
1

comme déjà dit, en java-8, nous avons la solution concise de Collectors:

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

et aussi, vous pouvez imbriquer plusieurs groupes en passant une autre méthode groupingBy comme deuxième paramètre:

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

De cette façon, nous aurons une carte à plusieurs niveaux, comme ceci: Map<key, Map<key, List<Item>>>

Andrea Scarafoni
la source
1

Utilisation de flux Java-8

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

J'aime la réponse de Kango_V, mais je pense que c'est trop complexe. Je pense que c'est plus simple - peut-être trop simple. S'il est incliné, vous pouvez remplacer String par un marqueur générique et le faire fonctionner pour n'importe quel type de clé.

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

Utilisé comme ceci:

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

                    @Override
                    public String getKeyForItem(PricingPlanAttribute item) {
                        return item.getFullName();
                    }
                } );
cs94njw
la source
0

Apache Commons MapUtils.populateMap

Si vous n'utilisez pas Java 8 et que vous ne souhaitez pas utiliser une boucle explicite pour une raison quelconque, essayez à MapUtils.populateMappartir d'Apache Commons.

MapUtils.populateMap

Disons que vous avez une liste de l' Pairart.

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

Et vous voulez maintenant une carte de la Pairclé de l' Pairobjet.

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

donne la sortie:

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

Cela étant dit, une forboucle est peut-être plus facile à comprendre. (Ce qui suit donne la même sortie):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
typoerrpr
la source