Java 8 Liste <V> in Map <K, V>

932

Ich möchte eine Liste von Objekten mit den Streams und Lambdas von Java 8 in eine Map übersetzen.

So würde ich es in Java 7 und darunter schreiben.

private Map<String, Choice> nameMap(List<Choice> choices) {
        final Map<String, Choice> hashMap = new HashMap<>();
        for (final Choice choice : choices) {
            hashMap.put(choice.getName(), choice);
        }
        return hashMap;
}

Ich kann dies leicht mit Java 8 und Guava erreichen, aber ich würde gerne wissen, wie man dies ohne Guava macht.

In Guave:

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, new Function<Choice, String>() {

        @Override
        public String apply(final Choice input) {
            return input.getName();
        }
    });
}

Und Guave mit Java 8 Lambdas.

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}
Tom Cammann
quelle

Antworten:

1394

Basierend auf der CollectorsDokumentation ist es so einfach wie:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
zapl
quelle
167
Nebenbei bemerkt, auch nach Java 8 kann das JDK noch nicht an einem Kürze-Wettbewerb teilnehmen. Die Guave-Alternative sieht so gut lesbar aus : Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac
3
Mit (statisch importiert) Seqaus der JOOL- Bibliothek (die ich jedem empfehlen würde, der Java 8 verwendet) können Sie die Kürze auch verbessern mit: seq(choices).toMap(Choice::getName)
lukens
5
Gibt es irgendwelche Vorteile bei der Verwendung von Function.identity? Ich meine, es -> es ist kürzer
Shabunc
9
@shabunc Ich kenne keinen Nutzen und benutze it -> itmich tatsächlich . Function.identity()wird hier hauptsächlich verwendet, weil es in der Dokumentation verwendet wird, auf die verwiesen wird, und das war alles, was ich zum Zeitpunkt des Schreibens über Lambdas wusste
zapl
12
@zapl, oh, tatsächlich stellt sich heraus, dass es Gründe dafür gibt - stackoverflow.com/questions/28032827/…
shabunc
307

Wenn nicht garantiert wird, dass Ihr Schlüssel für alle Elemente in der Liste eindeutig ist, sollten Sie ihn in a Map<String, List<Choice>>anstelle von a konvertierenMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));
Ulises
quelle
76
Dies gibt Ihnen tatsächlich Map <String, List <Auswahl >>, die sich mit der Möglichkeit nicht eindeutiger Schlüssel befasst, aber nicht das ist, was das OP angefordert hat. In Guava ist Multimaps.index (Auswahlmöglichkeiten, Choice :: getName) wahrscheinlich die bessere Option, wenn Sie dies sowieso möchten.
Richard Nichols
oder verwenden Sie lieber Guavas Multimap <String, Choice>, was in Szenarien sehr praktisch ist, in denen derselbe Schlüssel mehreren Werten zugeordnet ist. In Guava stehen verschiedene Dienstprogrammmethoden zur Verfügung, um solche Datenstrukturen zu verwenden, anstatt eine Map <String, List <Auswahl >>
Neeraj B.
1
@RichardNichols warum ist die Guavenmethode Multimapseine bessere Option? Dies kann eine Unannehmlichkeit sein, da kein MapObjekt zurückgegeben wird.
Theyuv
156

Verwenden Sie getName()als Schlüssel und sich Choiceselbst als Wert der Karte:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
Ole
quelle
19
Bitte schreiben Sie eine Beschreibung, damit der Benutzer sie verstehen kann.
Mayank Jain
4
Es ist wirklich schade, dass es hier nicht mehr Details gibt, weil mir diese Antwort am besten gefällt.
MadConan
Collectors.toMap(Choice::getName,c->c)(2 Zeichen kürzer)
Ferrybig
7
Es ist gleich choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); Erste Funktion für Schlüssel, zweite Funktion für Wert
Wasserwagen
16
Ich weiß, wie einfach es ist, zu sehen und zu verstehen, c -> caber es enthält Function.identity()mehr semantische Informationen. Ich benutze normalerweise einen statischen Import, damit ich nur verwenden kannidentity()
Hank D
28

Hier ist eine weitere für den Fall, dass Sie Collectors.toMap () nicht verwenden möchten.

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});
Emre Colak
quelle
1
Welches ist dann besser zu verwenden Collectors.toMap()oder unsere eigene HashMap, wie Sie im obigen Beispiel gezeigt haben?
Swapnil Gangrade
1
Dieses Beispiel lieferte ein Beispiel für das Platzieren von etwas anderem in der Karte. Ich wollte einen Wert, der nicht von einem Methodenaufruf bereitgestellt wird. Vielen Dank!
th3morg
1
Die dritte Argumentfunktion ist nicht korrekt. Dort sollten Sie eine Funktion bereitstellen, um zwei Hashmaps zusammenzuführen, so etwas wie Hashmap :: putAll
jesantana
25

Bei den meisten der aufgelisteten Antworten fehlt ein Fall, in dem die Liste doppelte Elemente enthält . In diesem Fall wird dort die Antwort geworfen IllegalStateException. Lesen Sie den folgenden Code , um auch Listenduplikate zu behandeln :

public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }
Sahil Chhabra
quelle
19

Eine weitere Option auf einfache Weise

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
Renukeswar
quelle
3
Es gibt keinen brauchbaren Unterschied bei der Verwendung dieses oder des Java 7-Typs.
Ashish Lohia
3
Ich fand das leichter zu lesen und zu verstehen, was vor sich ging als die anderen Mechanismen
Freiheit
2
Die SO fragte mit Java 8 Streams.
Mehraj Malik
18

Wenn Sie beispielsweise Objektfelder in Map konvertieren möchten:

Beispielobjekt:

class Item{
        private String code;
        private String name;

        public Item(String code, String name) {
            this.code = code;
            this.name = name;
        }

        //getters and setters
    }

Und Operation Liste in Karte konvertieren:

List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));

Map<String,String> map = list.stream()
     .collect(Collectors.toMap(Item::getCode, Item::getName));
Piotr R.
quelle
12

Wenn es Ihnen nichts ausmacht, Bibliotheken von Drittanbietern zu verwenden, enthält die Cyclops-React- Bibliothek von AOL (Offenlegung, an der ich beteiligt bin) Erweiterungen für alle JDK-Sammlungstypen , einschließlich Liste und Karte .

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);
John McClean
quelle
10

Ich habe versucht, dies zu tun, und festgestellt, dass ich bei Verwendung der obigen Antworten bei der Verwendung Functions.identity()des Schlüssels für die Karte Probleme mit der Verwendung einer lokalen Methode hatte this::localMethodName, die aufgrund von Tippproblemen tatsächlich funktioniert.

Functions.identity()hat in diesem Fall tatsächlich etwas mit der Eingabe zu tun, sodass die Methode nur funktioniert, wenn Objectein Parameter von zurückgegeben und akzeptiert wirdObject

Um dies zu lösen, habe ich stattdessen Schluss gemacht Functions.identity()und s->sstattdessen verwendet.

Mein Code, in meinem Fall, um alle Verzeichnisse in einem Verzeichnis aufzulisten und für jedes Verzeichnis den Namen des Verzeichnisses als Schlüssel für die Zuordnung zu verwenden und dann eine Methode mit dem Verzeichnisnamen aufzurufen und eine Sammlung von Elementen zurückzugeben, sieht folgendermaßen aus:

Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));
iZian
quelle
10

Sie können mit einem IntStream einen Stream der Indizes erstellen und diese dann in eine Map konvertieren:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));
Vikas Suryawanshi
quelle
1
Dies ist keine gute Option, da Sie für jedes Element einen Aufruf von get () ausführen, wodurch die Komplexität der Operation erhöht wird (o (n * k), wenn items eine Hashmap ist).
Nicolas Nobelis
Erhalten Sie (i) nicht auf einer Hashmap O (1)?
Ivo van der Veeken
@IvovanderVeeken das get (i) im Code-Snippet ist auf der Liste, nicht die Karte.
Zaki
@Zaki Ich habe über Nicolas 'Bemerkung gesprochen. Ich sehe die n * k-Komplexität nicht, wenn Elemente eine Hashmap anstelle einer Liste sind.
Ivo van der Veeken
8

Ich werde schreiben, wie man eine Liste mithilfe von Generika und Umkehrung der Steuerung in eine Karte konvertiert . Nur universelle Methode!

Vielleicht haben wir eine Liste von Ganzzahlen oder eine Liste von Objekten. Die Frage lautet also: Was sollte der Schlüssel der Karte sein?

Schnittstelle erstellen

public interface KeyFinder<K, E> {
    K getKey(E e);
}

jetzt mit Umkehrung der Kontrolle:

  static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
        return  list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
    }

Wenn wir beispielsweise Buchobjekte haben, soll diese Klasse den Schlüssel für die Karte auswählen

public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}
grep
quelle
6

Ich benutze diese Syntax

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
user2069723
quelle
11
groupingByschafft ein Map<K,List<V>>, nicht ein Map<K,V>.
Christoffer Hammarström
3
Dup of Ulises antworten. Und String getName();(nicht Integer)
Barett
5
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));
Kumar Abhishek
quelle
4

Dies kann auf zwei Arten erfolgen. Lassen Sie die Person die Klasse sein, mit der wir es demonstrieren werden.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Lassen Sie Personen die Liste der Personen sein, die in die Karte konvertiert werden sollen

1.Verwenden Sie Simple foreach und einen Lambda-Ausdruck in der Liste

Map<Integer,List<Person>> mapPersons = new HashMap<>();
persons.forEach(p->mapPersons.put(p.getAge(),p));

2.Verwenden von Collectors on Stream, die in der angegebenen Liste definiert sind.

 Map<Integer,List<Person>> mapPersons = 
           persons.stream().collect(Collectors.groupingBy(Person::getAge));
Raja Emani
quelle
4

Es ist möglich, Streams zu verwenden, um dies zu tun. Um die Notwendigkeit einer expliziten Verwendung zu beseitigen Collectors, ist ein toMapstatischer Import möglich (wie von Effective Java, dritte Ausgabe empfohlen).

import static java.util.stream.Collectors.toMap;

private static Map<String, Choice> nameMap(List<Choice> choices) {
    return choices.stream().collect(toMap(Choice::getName, it -> it));
}
Konrad Borowski
quelle
3

Hier ist eine Lösung von StreamEx

StreamEx.of(choices).toMap(Choice::getName, c -> c);
user_3380739
quelle
3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

Dient sogar diesem Zweck für mich,

Map<String,Choice> map=  list1.stream().collect(()-> new HashMap<String,Choice>(), 
            (r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));
Rajeev Akotkar
quelle
3

Wenn jeder neue Wert für denselben Schlüsselnamen überschrieben werden muss:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName,
            Function.identity(),
            (oldValue, newValue) - > newValue));
}

Wenn alle Auswahlmöglichkeiten in einer Liste für einen Namen zusammengefasst werden müssen:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}
Vaneet Kataria
quelle
1
List<V> choices; // your list
Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
//assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.
Vaibhav
quelle
0

Alternativ guavakann man kotlin-stdlib verwenden

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}
Frank Neblung
quelle
-1
String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
    List<String> list = Arrays.asList(array);
    Map<Integer, String> map = list.stream()
            .collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
                System.out.println("Dublicate key" + x);
                return x;
            },()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
    System.out.println(map);

Schlüssel AA veröffentlichen {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}

Ajay Kumar
quelle
2
Was versuchst du hier zu tun? Hast du die Frage gelesen?
Navderm