Java: So konvertieren Sie List in Map

224

Vor kurzem habe ich Gespräch mit einem Kollegen über das, was den optimalen Weg zu konvertieren wäre Listzu Mapin Java und wenn es spezifische Vorteile, dies zu tun.

Ich möchte einen optimalen Konvertierungsansatz kennen und würde mich sehr freuen, wenn mich jemand führen kann.

Ist das ein guter Ansatz:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}
Rachel
quelle
2
Was ist der beste optimale Weg? Die Optimierung erfolgt unter Berücksichtigung bestimmter Parameter (Geschwindigkeit / Speicher).
Daniel Fath
6
Die Liste unterscheidet sich konzeptionell von der Karte. Die Karte hat den Begriff "Schlüssel, Wert" -Paar, die Liste dagegen nicht. Angesichts dessen ist unklar, wie genau Sie von Liste zu Karte und zurück konvertieren werden.
Victor Sorokin
1
@ Daniel: Mit Optimal habe ich gemeint, was der beste Weg ist, dies unter all den verschiedenen Wegen zu tun. Ich bin mir nicht sicher, wie es geht, und es wäre gut, einige verschiedene Wege zu sehen, um Listen in Karten umzuwandeln.
Rachel
Mögliches Duplikat von Java: Wie konvertiere
ich
Lesen Sie diesen Artikel: So konvertieren Sie Liste in Map in Java
Lucky

Antworten:

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

Angenommen, jedes Element verfügt über eine getKey()Methode, die einen Schlüssel des richtigen Typs zurückgibt.

Jim Garrison
quelle
1
Sie können auch die Position in der Liste eingeben.
Jeremy
@ Jim: Muss ich das getKey()auf einen bestimmten Parameter einstellen ?
Rachel
Was wäre auch der Wert in der Karte? Können Sie ein Beispiel erläutern?
Rachel
1
@Rachel - Der Wert ist das Element in der Liste, und der Schlüssel macht das Element einzigartig, was von Ihnen festgelegt wird. Jims Gebrauch getKey()war willkürlich.
Jeremy
1
Sie kennen die Größe im Voraus, damit Sie Map <Key, Item> map = new HashMap <Key, Item> (list.size ()) ausführen können.
Víctor Romero
316

Mit Sie können dies in einer Zeile mithilfe von Streams und der CollectorsKlasse tun .

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

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

Ausgabe:

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

Wie in den Kommentaren erwähnt, können Sie Function.identity()anstelle von verwenden item -> item, obwohl ich i -> iziemlich explizit finde .

Um vollständig zu sein, beachten Sie, dass Sie einen binären Operator verwenden können, wenn Ihre Funktion nicht bijektiv ist. Betrachten wir zum Beispiel dies Listund die Zuordnungsfunktion, die für einen int-Wert das Ergebnis davon modulo 3 berechnet:

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

Wenn Sie diesen Code ausführen, wird eine Fehlermeldung angezeigt java.lang.IllegalStateException: Duplicate key 1. Dies liegt daran, dass 1% 3 gleich 4% 3 ist und daher bei gegebener Schlüsselzuordnungsfunktion denselben Schlüsselwert hat. In diesem Fall können Sie einen Zusammenführungsoperator angeben.

Hier ist eine, die die Werte summiert; (i1, i2) -> i1 + i2;das kann durch die Methodenreferenz ersetzt werden Integer::sum.

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

welches jetzt ausgibt:

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

Ich hoffe es hilft! :) :)

Alexis C.
quelle
19
besser verwenden Function.identity()stattitem -> item
Emmanuel Touzery
1
@ EmmanuelTouzery Nun, Function.identity()kehrt zurück t -> t;.
Alexis C.
2
Klar, beide funktionieren. Ich denke, es ist Geschmackssache. Ich finde Function.identity () sofort erkennbar.
Emmanuel Touzery
OP verarbeitet keine Pojos, nur Zeichenfolgen und Ganzzahlen, auf die Sie nicht zugreifen können ::getKey.
Phil294
@Blauhirn Ich weiß, mein Beispiel basiert auf der benutzerdefinierten Klasse unten. Sie können jede Funktion verwenden, um Ihre Schlüssel aus den Werten zu generieren.
Alexis C.
115

Nur für den Fall, dass diese Frage nicht als Duplikat geschlossen wird, ist die richtige Antwort die Verwendung von 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
quelle
17
Dies sollte an der Spitze sein
Martin Andersson
6
" Guava enthält eine streng kompatible Obermenge der alten, veralteten Google Collections Library . Sie sollten diese Bibliothek nicht mehr verwenden. " Möglicherweise ist ein Update erforderlich.
Winzig
3
Die Verwendung einer externen Bibliothek für eine so einfache Operation ist übertrieben. Das oder ein Zeichen einer sehr schwachen Standardbibliothek. In diesem Fall ist die Antwort von @ jim-garrison durchaus vernünftig. Es ist traurig, dass Java keine hilfreichen Methoden wie "Map" und "Reduce" hat, aber nicht unbedingt notwendig.
Linuxdan
2
Dies verwendet Guave. Leider ist Guava unter Android sehr langsam, daher sollte diese Lösung nicht in einem Android-Projekt verwendet werden.
IgorGanapolsky
Wenn das Element in der Liste Rollennamen dupliziert hat, löst der obige Code eine Ausnahme aus
Junchen Liu
17

Mit Java 8 können Sie Folgendes tun:

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

Value kann jedes Objekt sein, das Sie verwenden.

stackFan
quelle
16

Seit Java 8 wird die Antwort von @ZouZou mit demCollectors.toMap Kollektor sicherlich der idiomatische Weg, um dieses Problem zu lösen.

Und da dies eine so häufige Aufgabe ist, können wir daraus ein statisches Dienstprogramm machen.

Auf diese Weise wird die Lösung wirklich zu einem Einzeiler.

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

Und so würden Sie es auf einem verwenden List<Student>:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);
glänzt
quelle
Eine Diskussion der Typparameter dieser Methode finden Sie in meiner Folgefrage .
glitzert
Dies löst eine Ausnahme aus, falls doppelte Schlüssel vorhanden sind. Etw wie: Ausnahme im Thread "main" java.lang.IllegalStateException: Schlüssel duplizieren .... Für Details siehe: codecramp.com/java-8-streams-api-convert-list-map
EMM
@EMM Natürlich, wie beabsichtigt und im Javadoc dokumentiert.
glitzert
Ja, die Antwort wurde aktualisiert, um den Fall von Duplikaten abzudecken. Lesen Sie bitte. Vielen Dank
EMM
10

A Listund Mapsind konzeptionell unterschiedlich. A Listist eine bestellte Sammlung von Gegenständen. Die Elemente können Duplikate enthalten, und ein Element hat möglicherweise kein Konzept für eine eindeutige Kennung (Schlüssel). EINMap hat Werte, die Schlüsseln zugeordnet sind. Jeder Schlüssel kann nur auf einen Wert zeigen.

Abhängig von Ihren ListArtikeln kann es daher möglich sein, sie in a umzuwandeln Map. ListHaben Ihre Artikel keine Duplikate? Hat jeder Artikel einen eindeutigen Schlüssel? Wenn ja, dann ist es möglich, sie in eine zu setzen Map.

Steve Kuo
quelle
10

Alexis hat bereits eine Antwort in Java 8 mit der Methode veröffentlicht toMap(keyMapper, valueMapper). Gemäß Dokument für diese Methodenimplementierung:

Es gibt keine Garantie für den Typ, die Veränderlichkeit, die Serialisierbarkeit oder die Thread-Sicherheit der zurückgegebenen Map.

Wenn wir also an einer bestimmten Implementierung der MapSchnittstelle interessiert sind, HashMapkönnen wir das überladene Formular wie folgt verwenden:

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

Obwohl entweder mit Function.identity()oder in i->iOrdnung ist , aber es scheint Function.identity()statt i -> iMacht sparte etwas Speicher gemäß diesem Stand der Antwort .

akhil_mittal
quelle
1
Die lustige Tatsache, dass 2019 eine große Anzahl von Menschen immer noch nicht merkt, dass sie keine echte Kartenimplementierung kennen, die sie mit Lambda erhalten! Eigentlich ist dies nur eine Antwort, die ich mit Java 8 Lambdas gefunden habe, die ich für die Produktion verwenden würde.
Pavlo
Gibt es eine Möglichkeit zum Sammeln durch Angabe eines Kartentyps, jedoch ohne Verwendung einer Zusammenführungsfunktion?
Rosberg Linhares
8

Es gibt auch eine einfache Möglichkeit, dies mit Maps.uniqueIndex (...) zu tun. von Google zu Bibliotheken

Andrejs
quelle
5

Universelle Methode

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
quelle
Wie benutzt man das? Was soll ich als converterParameter in der Methode übergeben?
IgorGanapolsky
4

Ohne Java-8 können Sie dies in einzeiligen Commons-Sammlungen und in der Closure-Klasse tun

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
quelle
2

Je nachdem, was Sie erreichen möchten, fallen Ihnen viele Lösungen ein:

Jedes Listenelement ist Schlüssel und Wert

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

Listenelemente haben etwas zum Nachschlagen, vielleicht einen Namen:

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

Listenelemente können nachgeschlagen werden, und es gibt keine Garantie dafür, dass sie eindeutig sind: Verwenden Sie Googles MultiMaps

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

Geben Sie allen Elementen die Position als Schlüssel:

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

...

Es kommt wirklich darauf an, was Sie erreichen wollen.

Wie Sie den Beispielen entnehmen können, ist eine Karte eine Zuordnung von einem Schlüssel zu einem Wert, während eine Liste nur eine Reihe von Elementen mit jeweils einer Position ist. Sie sind also einfach nicht automatisch konvertierbar.

Daniel
quelle
Aber wir können die Position des Listenelements als Schlüssel betrachten und ihren Wert in die Karte einfügen. Ist dies eine gute Lösung?
Rachel
AFAIK ja! Es gibt keine Funktion im JDK, die dies automatisch ausführt. Sie müssen also Ihre eigene rollen.
Daniel
Ist es möglich, die letzte Version (unter Verwendung des Array-Index als Kartenschlüssel) mit Java 8-Streams zu erstellen?
Phil294
2

Hier ist eine kleine Methode, die ich genau für diesen Zweck geschrieben habe. Es verwendet Validate from Apache Commons.

Fühlen Sie sich frei, es zu benutzen.

/**
 * 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
quelle
2

Sie können die Streams-API von Java 8 nutzen.

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

Weitere Informationen finden Sie unter: http://codecramp.com/java-8-streams-api-convert-list-map/

EMM
quelle
1

Ein Java 8-Beispiel zum Konvertieren eines List<?>Objekts in ein 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 kopiert von:
https://www.mkyong.com/java8/java-8-convert-list-to-map/

Doss
quelle
1

Wie bereits gesagt, haben wir in Java-8 die prägnante Lösung von Collectors:

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

Außerdem können Sie mehrere Gruppen verschachteln, indem Sie eine andere groupingBy-Methode als zweiten Parameter übergeben:

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

Auf diese Weise erhalten wir eine mehrstufige Karte wie folgt: Map<key, Map<key, List<Item>>>

Andrea Scarafoni
quelle
1

Verwenden von Java-8-Streams

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

Ich mag die Antwort von Kango_V, aber ich denke, sie ist zu komplex. Ich denke, das ist einfacher - vielleicht zu einfach. Wenn Sie dazu neigen, können Sie String durch einen generischen Marker ersetzen und ihn für jeden Schlüsseltyp verwenden.

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

So verwendet:

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

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

Apache Commons MapUtils.populateMap

Wenn Sie Java 8 nicht verwenden und aus irgendeinem Grund keine explizite Schleife verwenden möchten, versuchen Sie es mit MapUtils.populateMapApache Commons.

MapUtils.populateMap

Angenommen, Sie haben eine Liste von Pairs.

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

Und Sie möchten jetzt eine Karte des PairSchlüssels des PairObjekts.

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

gibt Ausgabe:

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

Davon abgesehen ist eine forSchleife vielleicht leichter zu verstehen. (Dies unten gibt die gleiche Ausgabe):

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