Wie initialisiere ich eine HashMap direkt (wörtlich)?

1093

Gibt es eine Möglichkeit, eine Java HashMap wie diese zu initialisieren?:

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

Was wäre die richtige Syntax? Ich habe dazu nichts gefunden. Ist das möglich? Ich suche nach dem kürzesten / schnellsten Weg, um einige "endgültige / statische" Werte in eine Karte einzufügen, die sich nie ändern und die beim Erstellen der Karte im Voraus bekannt sind.

jens
quelle
Eng verwandt: stackoverflow.com/questions/507602/… (Bei beiden Fragen geht es um die Initialisierung einer konstanten Karte mit statischen Endwerten.)
Jonik
In Java 9: techiedelight.com/initialize-map-java9
Kamil Tomasz Jarmusik
Wenn Sie apache.commons.collections verwenden, können Sie commons.apache.org/proper/commons-collections/javadocs/…
ax verwenden.

Antworten:

1343

Alle Versionen

Für den Fall, dass Sie nur einen einzigen Eintrag benötigen: Es gibt Collections.singletonMap("key", "value").

Für Java Version 9 oder höher:

Ja, das ist jetzt möglich. In Java 9 wurden einige Factory-Methoden hinzugefügt, die das Erstellen von Karten vereinfachen:

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

Im obigen Beispiel sind beide testund test2gleich, nur mit unterschiedlichen Ausdrucksformen der Karte. Die Map.ofMethode ist für bis zu zehn Elemente in der Karte definiert, während die Map.ofEntriesMethode keine solche Begrenzung hat.

Beachten Sie, dass in diesem Fall die resultierende Karte eine unveränderliche Karte ist. Wenn Sie möchten, dass die Karte veränderbar ist, können Sie sie erneut kopieren, zmutableMap = new HashMap<>(Map.of("a", "b"));

(Siehe auch JEP 269 und Javadoc )

Für bis zu Java Version 8:

Nein, Sie müssen alle Elemente manuell hinzufügen. Sie können einen Initialisierer in einer anonymen Unterklasse verwenden, um die Syntax etwas zu verkürzen:

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

In einigen Fällen kann die anonyme Unterklasse jedoch zu unerwünschtem Verhalten führen. Dies beinhaltet zum Beispiel:

  • Es wird eine zusätzliche Klasse generiert, die den Speicherverbrauch, den Speicherplatzverbrauch und die Startzeit erhöht
  • Bei einer nicht statischen Methode: Sie enthält einen Verweis auf das Objekt, für das die Erstellungsmethode aufgerufen wurde. Das bedeutet, dass das Objekt der äußeren Klasse nicht durch Müll gesammelt werden kann, während auf das erstellte Kartenobjekt noch verwiesen wird, wodurch zusätzlicher Speicher blockiert wird

Wenn Sie eine Funktion zur Initialisierung verwenden, können Sie auch eine Karte in einem Initialisierer erstellen, aber böse Nebenwirkungen vermeiden:

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;
}
Yankee
quelle
3
Dies funktioniert nicht, wenn Sie die Elemente in einer Funktion
Michael
9
@ Michael: Nun ja, wenn Sie eine Funktion verwenden möchten, können Sie keine Nichtfunktion verwenden. Aber warum willst du?
Yankee
6
und für die Fälle, wenn Sie eine Karte mit einem einzigen Eintrag benötigen, gibt es Collections.singletonMap():)
skwisgaar
3
Nachdem das stabile Java 9 veröffentlicht wurde, bevorzuge ich diesen Link für Javadoc . Und +1 weil eine Abhängigkeit weniger!
Franklin Yu
3
Wo ist Java 9 entrydokumentiert?
Nobar
1029

Dies ist eine Möglichkeit.

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

Sie sollten jedoch vorsichtig sein und sicherstellen, dass Sie den obigen Code verstehen (es wird eine neue Klasse erstellt, die von HashMap erbt). Lesen Sie daher hier mehr: http://www.c2.com/cgi/wiki?DoubleBraceInitialization , oder verwenden Sie einfach Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
gregory561
quelle
72
Es funktioniert, ist aber hässlich und hat unsichtbare Nebenwirkungen, die der Benutzer vorher verstehen sollte - zum Beispiel das Generieren einer gesamten anonymen Klasse vor Ort.
jprete
96
Ja, so habe ich über Vorsicht geschrieben und einen Link zur Beschreibung gegeben.
Gregor561
6
Toller Link. Der Verweis in diesem Link auf GreencoddsTenthRuleOfProgramming ist lesenswert.
Michaelok
19
Können Sie "as ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" hinzufügen, da die "of" -Methode auf maximal 5 Paare begrenzt ist?
kommradHomer
342

Wenn Sie Bibliotheken von Drittanbietern zulassen, können Sie die ImmutableMap von Guava verwenden , um eine wörtliche Kürze zu erreichen:

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

Dies funktioniert für bis zu 5 Schlüssel / Wert-Paare , andernfalls können Sie den Builder verwenden :

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


  • Beachten Sie, dass sich die ImmutableMap- Implementierung von Guava von der HashMap- Implementierung von Java unterscheidet (insbesondere ist sie unveränderlich und erlaubt keine Nullschlüssel / -werte).
  • Weitere Informationen finden Sie im Guava-Benutzerhandbuch zu den unveränderlichen Sammlungstypen
Jens Hoffmann
quelle
26
Außerdem hat Guave ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius
17
ImmutableMap ist nicht dasselbe wie eine HashMap, da es bei Nullwerten fehlschlägt, während Map HashMap dies nicht tut.
Gewthen
2
Nur um anderen zu helfen, die mit diesem Problem konfrontiert sind. Sie müssen den Builder eingeben, um daraus eine Map <String, String> zu machen: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Thiago
Das ist großartig, Jens!
Gaurav
105

Es gibt keinen direkten Weg, dies zu tun - Java hat keine Map-Literale (noch - ich denke, sie wurden für Java 8 vorgeschlagen).

Manche Leute mögen das:

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

Dadurch wird eine anonyme Unterklasse von HashMap erstellt, deren Instanzinitialisierer diese Werte setzt. (Übrigens kann eine Karte nicht doppelt so viel Wert enthalten, Ihr zweiter Put überschreibt den ersten. Für die nächsten Beispiele verwende ich unterschiedliche Werte.)

Der normale Weg wäre folgender (für eine lokale Variable):

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

Wenn Ihre testMap eine Instanzvariable ist, fügen Sie die Initialisierung in einen Konstruktor oder Instanzinitialisierer ein:

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

Wenn Ihre testMap eine Klassenvariable ist, fügen Sie die Initialisierung in einen statischen Initialisierer ein:

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

Wenn Sie möchten, dass sich Ihre Karte niemals ändert, sollten Sie Ihre Karte nach der Initialisierung umschließen Collections.unmodifiableMap(...). Sie können dies auch in einem statischen Initialisierer tun:

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

(Ich bin mir nicht sicher, ob du jetzt das testFinale schaffen kannst ... probiere es aus und berichte hier.)

Paŭlo Ebermann
quelle
61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};
Zottiger Frosch
quelle
Einfach und auf den Punkt. Ich denke, dies mit einem erweiterten Kommentarbereich wäre die beste Antwort.
Ooolala
15
Es gibt jedoch Auswirkungen auf das Gedächtnis, die beachtet werden sollten. blog.jooq.org/2014/12/08/…
Amalgovinus
1
@Amalgovinus Grundsätzlich codieren Sie durch Erstellen einer neuen Unterklasse die Typargumente aus HashMapdieser Unterklasse fest. Dies kann nur funktionieren, wenn Sie sie tatsächlich bereitstellen. (Mit einer neuen (leeren) HashMap sind die Typargumente nicht relevant.)
Paŭlo Ebermann
1
Ich mag die Sauberkeit, aber es schafft unnötige anonyme Klasse und hat die hier beschriebenen Probleme: c2.com/cgi/wiki?DoubleBraceInitialization
udachny
1
@hello_its_me: Da es mit der Antwort von stackoverflow.com/a/6802512/1386911 identisch ist , unterscheidet sich nur die Formatierung. In diesem Fall hat diese erweiterte Formatierung zusätzlich zum kompakten Format keinen zusätzlichen Wert für die Lesbarkeit.
Daniel Hári
44

Eine Alternative mit einfachen Java 7-Klassen und -Varargs: Erstellen Sie eine Klasse HashMapBuildermit dieser Methode:

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

Verwenden Sie die Methode wie folgt:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
Aerthel
quelle
Ich habe eine von Ihnen inspirierte Antwort geschrieben: stackoverflow.com/questions/507602/…
GeroldBroser stellt Monica
1
Eine andere Lösung mit Apache Utils, die nie erwähnt wird, aber mit früheren Java-Versionen lesbar ist: MapUtils.putAll (neues HashMap <String, String> (), neues Objekt [] {"Mein Schlüssel", "Mein Wert", ...
Rolintocour
4

tl; dr

Verwenden Sie Map.of…Methoden in Java 9 und höher.

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

Map.of

Java 9 hat eine Reihe Map.ofstatischer Methoden hinzugefügt , um genau das zu tun, was Sie wollen: Instanziieren Sie eine unveränderlicheMap Methode mithilfe der Literal-Syntax .

Die Karte (eine Sammlung von Einträgen) ist unveränderlich, sodass Sie nach dem Instanziieren keine Einträge hinzufügen oder entfernen können. Auch der Schlüssel und der Wert jedes Eintrags sind unveränderlich, können nicht geändert werden. Weitere Regeln finden Sie im Javadoc , z. B. keine zulässigen NULL-Werte, keine zulässigen doppelten Schlüssel und die willkürliche Reihenfolge der Zuordnungen.

Schauen wir uns diese Methoden an und verwenden einige Beispieldaten für eine Karte des Wochentags für eine Person, von der wir erwarten, dass sie an diesem Tag arbeitet.

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

Map.of()

Map.oferstellt eine leere Map. Nicht änderbar, daher können Sie keine Einträge hinzufügen. Hier ist ein Beispiel für eine solche Karte, leer ohne Einträge.

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

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)Es gibt verschiedene Methoden, die 1 bis 10 Schlüssel-Wert-Paare annehmen. Hier ist ein Beispiel für zwei Einträge.

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

WeekendWorker.toString (): {SUNDAY = Person {Name = 'Bob'}, SAMSTAG = Person {Name = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )Nimmt eine beliebige Anzahl von Objekten, die die Map.EntrySchnittstelle implementieren . Java bündelt zwei Klassen, die diese Schnittstelle implementieren, eine veränderlich, die andere unveränderlich: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Wir müssen jedoch keine konkrete Klasse angeben. Wir müssen lediglich die Map.entry( k , v )Methode aufrufen , unseren Schlüssel und unseren Wert übergeben und erhalten ein Objekt einer Klasse zurück, die eine Map.EntrySchnittstelle implementiert .

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

weekdayWorker.toString (): {MITTWOCH = Person {Name = 'Bob'}, DIENSTAG = Person {Name = 'Bob'}, DONNERSTAG = Person {Name = 'Carol'}, FREITAG = Person {Name = 'Carol'} , MONTAG = Person {name = 'Alice'}}

Map.copyOf

Java 10 hat die Methode hinzugefügt Map.copyOf. Übergeben Sie eine vorhandene Karte und erhalten Sie eine unveränderliche Kopie dieser Karte zurück.

Anmerkungen

Beachten Sie, dass die Iterator Reihenfolge der Karten hergestellt via Map.ofist nicht garantiert. Die Einträge haben eine beliebige Reihenfolge. Schreiben Sie keinen Code basierend auf der angezeigten Reihenfolge, da die Dokumentation warnt, dass sich die Bestellung ändern kann.

Beachten Sie, dass alle diese Map.of…Methoden eine Rückkehr Mapvon einer nicht spezifizierten Klasse . Die zugrunde liegende konkrete Klasse kann sogar von einer Java-Version zur anderen variieren. Diese Anonymität ermöglicht es Java, aus verschiedenen Implementierungen auszuwählen, je nachdem, was optimal zu Ihren Daten passt. Wenn Ihre Schlüssel beispielsweise aus einer Enumeration stammen , verwendet Java möglicherweise ein EnumMapUnder-the-Cover.

Basil Bourque
quelle
1

Möglicherweise können Sie Ihre eigene Map.ofMethode (die nur in Java 9 und höher verfügbar ist) auf zwei einfache Arten erstellen

Machen Sie es mit einer festgelegten Anzahl von Parametern

Beispiel

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

Machen Sie es mit einer Liste

Sie können dies auch mithilfe einer Liste erstellen, anstatt viele Methoden für einen bestimmten Parametersatz zu erstellen.

Beispiel

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

Hinweis Es wird nicht empfohlen, dies für alles zu verwenden, da dies jedes Mal eine anonyme Klasse ergibt, wenn Sie dies verwenden.

NotNV6
quelle
1

JAVA 8

In Plain Java 8 haben Sie auch die Möglichkeit Streams/Collectors, die Arbeit zu erledigen.

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

Dies hat den Vorteil, dass keine anonyme Klasse erstellt wird.

Beachten Sie, dass die Importe sind:

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

Wie in anderen Antworten erwähnt, haben Sie ab Java 9 natürlich einfachere Möglichkeiten, dasselbe zu tun.

Johnny Willer
quelle
0

Leider ist die Verwendung von varargs, wenn der Typ der Schlüssel und Werte nicht identisch ist, nicht sehr sinnvoll, da Sie die Typensicherheit Object...vollständig verwenden und verlieren müssten . Wenn Sie immer zB a erstellen möchten Map<String, String>, natürlich atoMap(String... args) wäre möglich, aber nicht sehr hübsch, da es einfach wäre, Schlüssel und Werte zu verwechseln, und eine ungerade Anzahl von Argumenten ungültig wäre.

Sie können eine Unterklasse von HashMap erstellen, die eine verkettbare Methode wie hat

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

und benutze es gerne new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Ein anderer Ansatz besteht darin, das allgemeine Builder-Muster zu verwenden:

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

und benutze es gerne new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Die Lösung, die ich ab und zu verwendet habe, verwendet jedoch varargs und die PairKlasse:

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

Die Ausführlichkeit von Pair.create()stört mich ein bisschen, aber das funktioniert ganz gut. Wenn Ihnen statische Importe nichts ausmachen, können Sie natürlich einen Helfer erstellen:

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

(Anstelle der PairVerwendung könnte man sich vorstellen Map.Entry, aber da es sich um eine Schnittstelle handelt, ist eine implementierende Klasse und / oder eine Helfer-Factory-Methode erforderlich. Sie ist auch nicht unveränderlich und enthält andere Logik, die für diese Aufgabe nicht nützlich ist.)

JHH
quelle
0

Sie können Streams in Java 8 verwenden (dies ist ein Beispiel für Set):

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

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

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

Robocide
quelle
0

Wenn Sie nur ein Schlüssel-Wert-Paar platzieren müssen, können Sie Collections.singletonMap (Schlüssel, Wert) verwenden.

Balakrishna Kudikala
quelle
1
Formatierungscode macht den Beitrag viel lesbarer
Renato