Come inizializzare direttamente una HashMap (in modo letterale)?


1095

C'è un modo per inizializzare una HashMap Java come questa ?:

Map<String,String> test = 
    new HashMap<String, String>{"test":"test","test":"test"};

Quale sarebbe la sintassi corretta? Non ho trovato nulla al riguardo. È possibile? Sto cercando il modo più breve / veloce per inserire alcuni valori "finali / statici" in una mappa che non cambiano mai e sono noti in anticipo durante la creazione della mappa.



Strettamente correlato: stackoverflow.com/questions/507602/… (Entrambe le domande riguardano l'inizializzazione di una mappa costante con valori finali statici).
Jonik,



Se usi apache.commons.collections, puoi usare commons.apache.org/proper/commons-collections/javadocs/…
ax.

Risposte:


1347

Tutte le versioni

Nel caso in cui ti serva una sola voce: c'è Collections.singletonMap("key", "value").

Per Java versione 9 o successiva:

Sì, questo è possibile ora. In Java 9 sono stati aggiunti un paio di metodi di fabbrica che semplificano la creazione di mappe:

// this works for up to 10 elements:
Map<String, String> test1 = Map.of(
    "a", "b",
    "c", "d"
);

// this works for any number of elements:
import static java.util.Map.entry;    
Map<String, String> test2 = Map.ofEntries(
    entry("a", "b"),
    entry("c", "d")
);

Nell'esempio sopra entrambi teste test2sarà lo stesso, solo con modi diversi di esprimere la mappa. Il Map.ofmetodo è definito per un massimo di dieci elementi nella mappa, mentre il Map.ofEntriesmetodo non avrà tale limite.

Si noti che in questo caso la mappa risultante sarà una mappa immutabile. Se si desidera che la mappa sia mutabile, è possibile copiarla di nuovo, ad esempio utilizzandomutableMap = new HashMap<>(Map.of("a", "b"));

(Vedi anche JEP 269 e Javadoc )

Fino a Java versione 8:

No, dovrai aggiungere tutti gli elementi manualmente. È possibile utilizzare un inizializzatore in una sottoclasse anonima per ridurre leggermente la sintassi:

Map<String, String> myMap = new HashMap<String, String>() {{
        put("a", "b");
        put("c", "d");
    }};

Tuttavia, la sottoclasse anonima potrebbe introdurre comportamenti indesiderati in alcuni casi. Questo include ad esempio:

  • Genera una classe aggiuntiva che aumenta il consumo di memoria, il consumo di spazio su disco e il tempo di avvio
  • Nel caso di un metodo non statico: contiene un riferimento all'oggetto su cui è stato chiamato il metodo di creazione. Ciò significa che l'oggetto della classe esterna non può essere garbage collection mentre si fa ancora riferimento all'oggetto mappa creato, bloccando così la memoria aggiuntiva

L'uso di una funzione per l'inizializzazione ti consentirà anche di generare una mappa in un inizializzatore, ma evita i cattivi effetti collaterali:

Map<String, String> myMap = createMap();

private static Map<String, String> createMap() {
    Map<String,String> myMap = new HashMap<String,String>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    return myMap;
}

3
Questo non funzionerà se vuoi inizializzare gli elementi in una funzione ...
Michael

9
@Michael: Beh, sì, se vuoi usare una funzione che non puoi usare una non-funzione. Ma perché lo vuoi?
yankee,

6
e per i casi in cui hai bisogno di una mappa con una sola voce c'è Collections.singletonMap():)
skwisgaar

3
Ora che Java 9 è stato rilasciato, preferisco questo link per Javadoc . E +1 perché una dipendenza in meno!
Franklin Yu,

3
Dove è entrydocumentato Java 9 ?
nobar,

1030

Questo è un modo.

HashMap<String, String> h = new HashMap<String, String>() {{
    put("a","b");
}};

Tuttavia, dovresti stare attento e assicurarti di comprendere il codice sopra (crea una nuova classe che eredita da HashMap). Pertanto, dovresti leggere di più qui: http://www.c2.com/cgi/wiki?DoubleBraceInitialization o semplicemente utilizzare Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);

72
Funziona ma è brutto e ha effetti collaterali invisibili che l'utente dovrebbe capire prima di farlo - ad esempio, generare un'intera classe anonima sul posto.
giovedì

96
sì, è così che ho scritto sull'attenzione e ho fornito un link alla descrizione.
Gregory561,

6
Ottimo collegamento Vale la pena leggere il riferimento in quel link a GreencoddsTenthRuleOfProgramming .
michaelok,

19
puoi aggiungere "as ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" poiché il metodo "of" è limitato a massimo 5 coppie?
kommradHomer,


342

Se si consente 3rd librerie di partito, è possibile utilizzare Guava 's ImmutableMap per raggiungere letterale come brevità:

Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");

Funziona con un massimo di 5 coppie chiave / valore , altrimenti puoi usare il suo builder :

Map<String, String> test = ImmutableMap.<String, String>builder()
    .put("k1", "v1")
    .put("k2", "v2")
    ...
    .build();


  • nota che di Guava ImmutableMap attuazione differisce da Java HashMap attuazione (in particolare è immutabile e non consente chiavi nulli / valori)
  • per ulteriori informazioni, consultare l'articolo della guida dell'utente di Guava sui suoi immutabili tipi di raccolta

26
Inoltre, guava ha ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius

17
ImmutableMap non è la stessa di una HashMap, poiché fallirà su valori null, mentre la mappa HashMap no.
Gewthen,

2
Solo per aiutare gli altri che potrebbero affrontare questo problema. Devi digitare il builder per renderlo una mappa <String, String>, in questo modo: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Thiago,

questo è fantastico Jens!
gaurav

105

Non esiste un modo diretto per farlo - Java non ha letterali Map (ancora - penso che siano stati proposti per Java 8).

Ad alcune persone piace questo:

Map<String,String> test = new HashMap<String, String>(){{
       put("test","test"); put("test","test");}};

Questo crea una sottoclasse anonima di HashMap, il cui inizializzatore dell'istanza inserisce questi valori. (A proposito, una mappa non può contenere il doppio dello stesso valore, il tuo secondo put sovrascriverà il primo. Userò valori diversi per i prossimi esempi.)

Il modo normale sarebbe questo (per una variabile locale):

Map<String,String> test = new HashMap<String, String>();
test.put("test","test");
test.put("test1","test2");

Se la tua testmappa è una variabile di istanza, inserisci l'inizializzazione in un costruttore o in un inizializzatore di istanza:

Map<String,String> test = new HashMap<String, String>();
{
    test.put("test","test");
    test.put("test1","test2");
}

Se la tua testmappa è una variabile di classe, inserisci l'inizializzazione in un inizializzatore statico:

static Map<String,String> test = new HashMap<String, String>();
static {
    test.put("test","test");
    test.put("test1","test2");
}

Se vuoi che la tua mappa non cambi mai, dovresti avvolgerla dopo l'inizializzazione Collections.unmodifiableMap(...). Puoi farlo anche in un inizializzatore statico:

static Map<String,String> test;
{
    Map<String,String> temp = new HashMap<String, String>();
    temp.put("test","test");
    temp.put("test1","test2");
    test = Collections.unmodifiableMap(temp);
}

(Non sono sicuro che ora puoi rendere testdefinitivo ... provalo e segnala qui.)


61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};

Semplice e al punto. Penso che questo con una sezione di commento estesa sarebbe la risposta migliore.
Ooolala,

15
Ci sono implicazioni di memoria che dovrebbero essere notate però. blog.jooq.org/2014/12/12/…
Amalgovinus

1
@Amalgovinus Fondamentalmente, creando una nuova sottoclasse, si codifica a fondo gli argomenti del tipo da HashMapquesta sottoclasse. Questo può funzionare solo se li fornisci effettivamente. (Con una nuova (vuota) HashMap, gli argomenti del tipo non sono rilevanti.)
Paŭlo Ebermann

1
Mi piace la pulizia, ma crea una classe anonima non necessaria e presenta i problemi descritti qui: c2.com/cgi/wiki?DoubleBraceInitialization
udachny

1
@hello_its_me: Perché è uguale a stackoverflow.com/a/6802512/1386911 risposta, solo la formattazione è diversa. E in questo caso questa formattazione estesa non ha alcun valore aggiuntivo oltre al formato compatto per la leggibilità.
Daniel Hári,

44

Un'alternativa, usando semplici classi e vararg Java 7: creare una classe HashMapBuildercon questo metodo:

public static HashMap<String, String> build(String... data){
    HashMap<String, String> result = new HashMap<String, String>();

    if(data.length % 2 != 0) 
        throw new IllegalArgumentException("Odd number of arguments");      

    String key = null;
    Integer step = -1;

    for(String value : data){
        step++;
        switch(step % 2){
        case 0: 
            if(value == null)
                throw new IllegalArgumentException("Null key value"); 
            key = value;
            continue;
        case 1:             
            result.put(key, value);
            break;
        }
    }

    return result;
}

Usa il metodo in questo modo:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");

Ho scritto una risposta ispirata dal sottoscritto: stackoverflow.com/questions/507602/...
GeroldBroser ripristina Monica

1
Un'altra soluzione con Apache Utils che non è mai stata menzionata ma è leggibile, utilizzando le versioni Java precedenti: MapUtils.putAll (new HashMap <String, String> (), new Object [] {"My key", "my value", ...
Rolintocour,

4

tl; dr

Utilizzare i Map.of…metodi in Java 9 e versioni successive.

Map< String , String > animalSounds =
    Map.of(
        "dog"  , "bark" ,   // key , value
        "cat"  , "meow" ,   // key , value
        "bird" , "chirp"    // key , value
    )
;

Map.of

Java 9 ha aggiunto una serie di Map.ofmetodi statici per fare esattamente quello che vuoi: creare un'istanza immutabile Mapusando la sintassi letterale .

La mappa (una raccolta di voci) è immutabile, quindi non è possibile aggiungere o rimuovere voci dopo l'istanza. Inoltre, la chiave e il valore di ciascuna voce sono immutabili, non possono essere modificati. Vedi Javadoc per altre regole, come nessun NULL consentito, nessuna chiave duplicata consentita e l'ordine di iterazione dei mapping è arbitrario.

Diamo un'occhiata a questi metodi, usando alcuni dati di esempio per una mappa del giorno della settimana a una persona che prevediamo funzionerà quel giorno.

Person alice = new Person( "Alice" );
Person bob = new Person( "Bob" );
Person carol = new Person( "Carol" );

Map.of()

Map.ofcrea un vuoto Map. Non modificabile, quindi non è possibile aggiungere voci. Ecco un esempio di tale mappa, vuota senza voci.

Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)sono diversi metodi che accettano da 1 a 10 coppie chiave-valore. Ecco un esempio di due voci.

Map < DayOfWeek, Person > weekendWorker = 
        Map.of( 
            DayOfWeek.SATURDAY , alice ,     // key , value
            DayOfWeek.SUNDAY , bob           // key , value
        )
;

weekendWorker.toString (): {SUNDAY = Person {name = 'Bob'}, SABATO = Person {name = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )accetta qualsiasi numero di oggetti implementando l' Map.Entryinterfaccia. Java raggruppa due classi che implementano quell'interfaccia, una mutabile e l'altra immutabile: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Ma non è necessario specificare una classe concreta. Dobbiamo semplicemente chiamare il Map.entry( k , v )metodo, passare la nostra chiave e il nostro valore e recuperiamo un oggetto di Map.Entryun'interfaccia di implementazione di qualche classe .

Map < DayOfWeek, Person > weekdayWorker = Map.ofEntries(
        Map.entry( DayOfWeek.MONDAY , alice ) ,            // Call to `Map.entry` method returns an object implementing `Map.Entry`. 
        Map.entry( DayOfWeek.TUESDAY , bob ) ,
        Map.entry( DayOfWeek.WEDNESDAY , bob ) ,
        Map.entry( DayOfWeek.THURSDAY , carol ) ,
        Map.entry( DayOfWeek.FRIDAY , carol )
);

ferialeWorker.toString (): {WEDNESDAY = Person {name = 'Bob'}, MARTEDÌ = Person {name = 'Bob'}, THURSDAY = Person {name = 'Carol'}, FRIDAY = Person {name = 'Carol'} , LUNEDÌ = Person {name = 'Alice'}}

Map.copyOf

Java 10 ha aggiunto il metodo Map.copyOf. Passa una mappa esistente, ottieni una copia immutabile di quella mappa.

Appunti

Si noti che l'ordine delle mappe iteratore prodotta tramite Map.ofsono non garantiti. Le voci hanno un ordine arbitrario. Non scrivere il codice in base all'ordine visualizzato, poiché la documentazione avverte che l'ordine è soggetto a modifiche.

Si noti che tutti questi Map.of…metodi restituiscono un Mapdi una classe non specificata . La classe concreta sottostante può persino variare da una versione di Java a un'altra. Questo anonimato consente a Java di scegliere tra varie implementazioni, qualunque cosa si adatti in modo ottimale ai tuoi dati particolari. Ad esempio, se le tue chiavi provengono da un enum , Java potrebbe usare una EnumMapsotto le copertine.


1

Potresti eventualmente creare il tuo Map.ofmetodo (che è disponibile solo in Java 9 e versioni successive) facilmente in 2 semplici modi

Fallo con una quantità impostata di parametri

Esempio

public <K,V> Map<K,V> mapOf(K k1, V v1, K k2, V v2 /* perhaps more parameters */) {
    return new HashMap<K, V>() {{
      put(k1, v1);
      put(k2,  v2);
      // etc...
    }};
}

Fallo usando un elenco

Puoi anche farlo usando un elenco, invece di creare molti metodi per un determinato set di parametri.

Esempio

public <K, V> Map<K, V> mapOf(List<K> keys, List<V> values) {
   if(keys.size() != values.size()) {
        throw new IndexOutOfBoundsException("amount of keys and values is not equal");
    }

    return new HashMap<K, V>() {{
        IntStream.range(0, keys.size()).forEach(index -> put(keys.get(index), values.get(index)));
    }};
}

Nota Non è consigliabile utilizzarlo per tutto in quanto crea una classe anonima ogni volta che lo si utilizza.


1

JAVA 8

In Plain Java 8 hai anche la possibilità di usare Streams/Collectorsper fare il lavoro.

Map<String, String> myMap = Stream.of(
         new SimpleEntry<>("key1", "value1"),
         new SimpleEntry<>("key2", "value2"),
         new SimpleEntry<>("key3", "value3"))
        .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));

Questo ha il vantaggio di non creare una classe anonima.

Si noti che le importazioni sono:

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

Ovviamente, come notato in altre risposte, da java 9 in poi hai modi più semplici di fare lo stesso.


0

Sfortunatamente, usare varargs se il tipo di chiavi e valori non è lo stesso non è molto ragionevole in quanto dovresti usare Object...e perdere completamente la sicurezza del tipo. Se si desidera sempre creare, ad esempio Map<String, String>, un toMap(String... args)sarebbe ovviamente possibile, ma non molto carino in quanto sarebbe facile confondere chiavi e valori e un numero dispari di argomenti non sarebbe valido.

È possibile creare una sottoclasse di HashMap che ha un metodo concatenabile come

public class ChainableMap<K, V> extends HashMap<K, V> {
  public ChainableMap<K, V> set(K k, V v) {
    put(k, v);
    return this;
  }
}

e usalo come new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Un altro approccio consiste nell'utilizzare il modello di builder comune:

public class MapBuilder<K, V> {
  private Map<K, V> mMap = new HashMap<>();

  public MapBuilder<K, V> put(K k, V v) {
    mMap.put(k, v);
    return this;
  }

  public Map<K, V> build() {
    return mMap;
  }
}

e usalo come new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Tuttavia, la soluzione che ho usato ora e quindi utilizza varargs e la Pairclasse:

public class Maps {
  public static <K, V> Map<K, V> of(Pair<K, V>... pairs) {
    Map<K, V> = new HashMap<>();

    for (Pair<K, V> pair : pairs) {
      map.put(pair.first, pair.second);
    }

    return map;
  }
}

Map<String, Object> map = Maps.of(Pair.create("a", 1), Pair.create("b", "foo");

La verbosità di Pair.create()me mi disturba un po ', ma funziona abbastanza bene. Se non ti importano le importazioni statiche, puoi ovviamente creare un aiuto:

public <K, V> Pair<K, V> p(K k, V v) {
  return Pair.create(k, v);
}

Map<String, Object> map = Maps.of(p("a", 1), p("b", "foo");

(Invece di quanto Pairsi possa immaginare di usare Map.Entry, ma poiché è un'interfaccia richiede una classe di implementazione e / o un metodo factory di supporto. Inoltre non è immutabile e contiene altra logica non utile per questo compito.)


0

Puoi usare Streams In Java 8 (questo è un esempio di Set):

@Test
public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
    Set<String> countries = Stream.of("India", "USSR", "USA")
      .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

    assertTrue(countries.contains("India"));
}

Rif: https://www.baeldung.com/java-double-brace-initialization


0

Se è necessario posizionare solo una coppia chiave-valore, è possibile utilizzare Collections.singletonMap (chiave, valore);


1
il codice di formattazione rende i post molto più leggibili
Renato
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.