Java: Comment convertir une liste en carte
Récemment, j'ai une conversation avec un collègue sur ce qui serait le moyen optimal de convertir List
en Map
en Java et s'il y a des avantages spécifiques à le faire.
Je veux connaître l'approche de conversion optimale et j'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]);
}
16 answers
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é.
Avec java-8, vous serez en mesure de le faire à une ligne à l'aide de ruisseaux, et la Collectors
classe.
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 {[10] } et la fonction de mappage que pour un int valeur, calculer le résultat de celui-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 disant java.lang.IllegalStateException: Duplicate key 1
. En effet, 1% 3 est identique à 4% 3 et a donc la même valeur de clé étant donné 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 sort 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! :)
Juste au cas où cette question n'est pas fermée en tant que duplicata, la bonne réponse est d'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
}});
Depuis Java 8, la réponse par @ZouZou en utilisant le collecteur Collectors.toMap
est certainement la façon 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 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 pouvez l'utiliser sur un List<Student>
:
Map<Long, Student> studentsById = toMapBy(students, Student::getId);
Un List
et Map
sont conceptuellement différentes. Un 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é). A Map
a des valeurs mappées sur des clés. Chaque clé ne peut pointer que vers une seule valeur.
Par conséquent, en fonction des éléments de votre List
, il peut ou non être possible de le convertir en Map
. Les éléments de votre List
n'ont-ils pas de doublons? Chaque article a-t-il une clé unique? Si oui alors il est possible de les mettre dans un Map
.
Il existe également un moyen simple de le faire en utilisant Maps.uniqueIndex(...) à partir de Google goyave bibliothèques
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);
}
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.
Sans java-8, vous pourrez le faire dans une ligne Commons collections, et 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);
}
});
}};
Alexis a déjà posté une réponse dans Java 8 l'aide de la méthode toMap(keyMapper, valueMapper)
. Selon doc pour l'implémentation de cette méthode:
Il n'y a aucune garantie sur le type, la mutabilité, la sérialisabilité, ou fil de sécurité de la Carte retournée.
Donc, dans le cas où nous sommes intéressés par une implémentation spécifique de Map
interface par exemple {[3] } 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 soit Function.identity()
ou i->i
est bien, mais il semble Function.identity()
au lieu de i -> i
pourrait économiser de la mémoire selon cette réponse .
De nombreuses solutions viennent à l'esprit, en fonction de ce que vous voulez 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 pour les rechercher, peut-être un nom:
for( MyObject o : list ) {
map.put(o.name,o);
}
Les éléments de liste ont quelque chose pour les rechercher, et il n'y a aucune garantie qu'ils sont uniques: Utilisez Googles MultiMaps
for( MyObject o : list ) {
multimap.put(o.name,o);
}
Donnant à 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 archive.
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 automatiquement convertibles.
Voici une petite méthode que j'ai écrite dans ce but. Il utilise Validate à partir d'Apache Commons.
Sentir 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;
}
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/
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()));
Le Code copié à partir de:
https://www.mkyong.com/java8/java-8-convert-list-to-map/
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();
}
} );
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
depuis Apache Commons.
, Dire que vous avez une liste de Pair
s.
List<ImmutablePair<String, String>> pairs = ImmutableList.of(
new ImmutablePair<>("A", "aaa"),
new ImmutablePair<>("B", "bbb")
);
Et vous voulez maintenant une carte de la clé de Pair
à 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 étant dit, une boucle for
est peut-être plus facile à comprendre. (Ceci ci-dessous donne la même chose de sortie):
Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
map.put(pair.getKey(), pair);
}
System.out.println(map);