Elenco Java 8 <V> nella mappa <K, V>


933

Voglio tradurre un elenco di oggetti in una mappa usando i flussi e le lambda di Java 8.

Ecco come lo scriverei in Java 7 e versioni successive.

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

Posso farlo facilmente usando Java 8 e Guava ma vorrei sapere come farlo senza Guava.

In guava:

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

E Guava con Java 8 lambdas.

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

Risposte:


1395

Basato sulla Collectorsdocumentazione è semplice come:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));

167
Come nota a margine, anche dopo Java 8, il JDK non può ancora competere in un concorso di brevità. La Guava sguardi alternativi così tanto leggibile: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac,

3
Usando (importato staticamente) Seqdalla libreria JOOL (che consiglierei a chiunque usi Java 8), puoi anche migliorare la brevità con: seq(choices).toMap(Choice::getName)
lukens

5
Ci sono dei vantaggi nell'usare Function.identity? Voglio dire, è -> è più breve
shabunc

9
@shabunc Non conosco alcun vantaggio e effettivamente uso it -> itme stesso. Function.identity()viene utilizzato qui principalmente perché è utilizzato nella documentazione di riferimento e questo era tutto ciò che sapevo di lambda al momento della scrittura
zapl

12
@zapl, oh, in realtà si scopre ci sono ragioni alla base di questo - stackoverflow.com/questions/28032827/...
shabunc

307

Se la chiave NON è garantita come unica per tutti gli elementi nell'elenco, è necessario convertirla in un Map<String, List<Choice>>anziché in unMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));

76
Questo in realtà ti dà Map <String, List <Choice>> che si occupa della possibilità di chiavi non univoche, ma non è ciò che l'OP ha richiesto. In Guava, Multimaps.index (scelte, Scelta :: getName) è probabilmente un'opzione migliore se questo è quello che vuoi comunque.
Richard Nichols,

o meglio usare Guim's Multimap <String, Choice> che risulta abbastanza utile in scenari in cui la stessa chiave è associata a più valori. Ci sono vari metodi di utilità prontamente disponibili in Guava per usare tali strutture di dati piuttosto che creare una mappa <String, List <Choice>>
Neeraj B.

1
@RichardNichols perché il Multimapsmetodo guava è un'opzione migliore? Può essere un inconveniente poiché non restituisce un Mapoggetto.
Loro

156

Utilizzare getName()come chiave e Choicese stesso come valore della mappa:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));

19
Scrivi una descrizione in modo che l'utente possa capire.
Mayank Jain,

4
È davvero un peccato che non ci siano più dettagli qui, perché questa risposta mi piace di più.
MadConan,

Collectors.toMap(Choice::getName,c->c)(2 caratteri più brevi)
Ferrybig,

7
È uguale alla choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); prima funzione per chiave, seconda funzione per valore
Waterscar

16
So quanto sia facile vedere e capire, c -> cma Function.identity()trasporta più informazioni semantiche. Di solito uso un'importazione statica in modo che io possa semplicemente usareidentity()
Hank D

28

Eccone un altro nel caso in cui non si desideri utilizzare Collectors.toMap ()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});

1
Quale è meglio usare allora Collectors.toMap()o la nostra HashMap come mostrato nell'esempio sopra?
Swapnil Gangrade,

1
Questo esempio ha fornito un esempio di come posizionare qualcos'altro nella mappa. Volevo un valore non fornito da una chiamata di metodo. Grazie!
thmmorg

1
La funzione del terzo argomento non è corretta. Lì dovresti fornire una funzione per unire due hashmap, qualcosa come Hashmap :: putAll
jesantana,

25

Nella maggior parte delle risposte elencate, manca un caso in cui l' elenco contiene elementi duplicati . In tal caso la risposta verrà lanciata IllegalStateException. Fare riferimento al codice seguente per gestire anche i duplicati dell'elenco :

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

19

Un'altra opzione in modo semplice

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));

3
Non vi è alcuna differenza praticabile nell'uso di questo o java 7 tipo.
Ashish Lohia,

3
Ho trovato questo più facile da leggere e capire cosa stesse succedendo rispetto agli altri meccanismi
Freiheit,

2
Il SO ha chiesto con Java 8 Streams.
Mehraj Malik,

18

Ad esempio, se si desidera convertire i campi oggetto da mappare:

Oggetto di esempio:

class Item{
        private String code;
        private String name;

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

        //getters and setters
    }

E l'operazione converti la lista in mappa:

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

12

Se non ti dispiace usare le librerie di terze parti, la libreria di reazione al ciclope di AOL (rivelazione che sono un collaboratore) ha estensioni per tutti i tipi di raccolta JDK , inclusi Elenco e Mappa .

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

10

Stavo provando a fare questo e ho scoperto che, usando le risposte sopra, quando Functions.identity()ho usato per la chiave della mappa, ho avuto problemi con l'uso di un metodo locale come this::localMethodNamelavorare effettivamente a causa di problemi di battitura.

Functions.identity()in realtà fa qualcosa per la digitazione in questo caso, quindi il metodo funzionerebbe solo restituendo Objecte accettando un parametro diObject

Per risolvere questo, ho finito per abbandonare Functions.identity()e usare s->sinvece.

Quindi il mio codice, nel mio caso per elencare tutte le directory all'interno di una directory, e per ognuna utilizzare il nome della directory come chiave per la mappa e quindi chiamare un metodo con il nome della directory e restituire una raccolta di elementi, assomiglia a:

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

10

È possibile creare un flusso di indici utilizzando un IntStream e convertirli in una mappa:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));

1
Questa non è una buona opzione perché fai una chiamata get () per ogni elemento, aumentando quindi la complessità dell'operazione (o (n * k) se gli elementi sono una hashmap).
Nicolas Nobelis,

Non è possibile ottenere (i) su una hashmap O (1)?
Ivo van der Veeken,

@IvovanderVeeken get (i) nello snippet di codice è nell'elenco, non sulla mappa.
Zaki,

@Zaki Stavo parlando dell'osservazione di Nicolas. Non vedo la complessità di n * k se gli elementi sono una hashmap anziché un elenco.
Ivo van der Veeken,

8

Scriverò come convertire la lista in mappa usando i generici e l' inversione del controllo . Solo un metodo universale!

Forse abbiamo un elenco di numeri interi o un elenco di oggetti. Quindi la domanda è la seguente: quale dovrebbe essere la chiave della mappa?

creare un'interfaccia

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

ora usando l'inversione del controllo:

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

Ad esempio, se abbiamo oggetti di libro, questa classe deve scegliere la chiave per la mappa

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

6

Uso questa sintassi

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));

11
groupingBycrea un Map<K,List<V>>, non un Map<K,V>.
Christoffer Hammarström,

3
Dup of ulises risponde. E, String getName();(non intero)
Barett,

5
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));

4

Questo può essere fatto in 2 modi. Lascia che la persona sia la classe che useremo per dimostrarla.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Consenti alle persone di essere l'elenco delle persone da convertire nella mappa

1.Utilizzando Simple foreach e un'espressione Lambda nell'elenco

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

2.Utilizzo di Collezionisti su Stream definiti nell'elenco specificato.

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

4

È possibile utilizzare i flussi per farlo. Per eliminare la necessità di utilizzare esplicitamente Collectors, è possibile importare toMapstaticamente (come raccomandato da Effective Java, terza edizione).

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


3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

Mi serve anche questo scopo,

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

3

Se ogni nuovo valore con lo stesso nome chiave deve essere sovrascritto:

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

Se tutte le scelte devono essere raggruppate in un elenco per un nome:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}

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.

0

In alternativa a guavauno può usare kotlin-stdlib

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}

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

Chiave pubblica AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}


2
Cosa stai cercando di fare qui ?? Hai letto la domanda?
navderm
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.