web-dev-qa-db-fra.com

Java: Comment convertir une liste en carte

Récemment, je me suis entretenu avec un collègue sur le meilleur moyen de convertir List en Map en Java et d’éventuels avantages à le faire.

Je veux connaître l'approche de conversion optimale et apprécierais vraiment si quelqu'un peut 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]);
}
171
Rachel
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 a une méthode getKey() qui renvoie une clé du type approprié.

166
Jim Garrison

Avec Java-8 , vous pourrez le faire en une seule ligne en utilisant streams et le Collectors class.

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

Courte démo:

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

Sortie:

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 Function.identity() au lieu de item -> item, bien que je trouve i -> i plutô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 List et la fonction de mappage qui, pour une valeur int, en calcule le résultat 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));

Lorsque vous exécutez 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é pour la fonction de mappage de clé. Dans ce cas, vous pouvez fournir un opérateur de fusion.

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

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

qui produit maintenant:

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

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

238
Alexis C.

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

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

Et comme il s’agit d’une tâche si commune, nous pouvons en faire un utilitaire statique.

De cette façon, la solution devient vraiment un one-liner.

/**
 * 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 vous l'utiliseriez sur un List<Student>:

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

List et Map sont conceptuellement différents. Une List est une collection ordonnée d'éléments. Les éléments peuvent contenir des doublons et un élément peut ne pas avoir de concept d'identifiant unique (clé). Un Map a des valeurs mappées sur des clés. Chaque clé ne peut pointer que vers une valeur.

Par conséquent, en fonction des éléments de votre List, il peut être impossible ou non de le convertir en Map. Les articles de votre List n'ont-ils pas de doublons? Chaque article a-t-il une clé unique? Si c'est le cas, il est possible de les mettre dans une Map.

9
Steve Kuo

Il existe également un moyen simple de le faire en utilisant Maps.uniqueIndex (...) à partir de Google guava libraries

8
Andrejs

En utilisant Java 8, vous pouvez effectuer les tâches suivantes: 

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

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

8
stackFan

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

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

Il n'y a aucune garantie sur le type, la mutabilité, la sérialisabilité ou thread-safety de la carte renvoyée.

Donc, si nous sommes intéressés par une implémentation spécifique de l’interface Map, par ex. HashMap alors nous 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 Function.identity() ou i->i soit correcte, il semble que Function.identity() au lieu de i -> i pourrait économiser de la mémoire, conformément à cette relation avec { réponse } _.

4
i_am_zero

Sans Java-8, vous pourrez le faire dans une seule ligne Collections communes et 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);
        }
    });
}};
3
Vitaliy Oliynyk

Vous pouvez exploiter l'API de flux 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/

2
EMM

Voici une petite méthode que j'ai écrite dans ce but précis. 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;
}
2
Kango_V

Plusieurs solutions vous viennent à l’esprit, en fonction de vos objectifs:

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

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

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

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

Les éléments de la liste ont quelque chose à regarder, et rien ne garantit qu'ils sont uniques: Utilisez Googles MultiMaps

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

Donner à tous les éléments la position en tant que clé:

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

...

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

Comme vous pouvez le constater à partir des exemples, une carte est un mappage d'une clé à une valeur, tandis qu'une liste est simplement une série d'éléments ayant chacun une position. Donc, ils ne sont tout simplement pas automatiquement convertibles.

2
Daniel

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

Code copié de:
https://www.mkyong.com/Java8/Java-8-convert-list-to-map/

1
Doss

comme déjà dit, dans 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 second 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>>>

1
Andrea Scarafoni

Apache Commons MapUtils.populateMap

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

MapUtils.populateMap

Supposons que vous ayez une liste de Pairs.

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

Et vous voulez maintenant une carte de la clé Pair de l'objet 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);

donne la sortie:

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

Cela dit, une boucle for est peut-être plus facile à comprendre. (Ce qui suit donne le même résultat):

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

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. Si vous êtes incliné, vous pouvez remplacer String par un marqueur générique et le faire fonctionner pour tout 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();
                    }
                } );
0
cs94njw