Wie kann ich eine statische Karte initialisieren?


1131

Wie würden Sie eine Statik Mapin Java initialisieren ?

Methode eins: statischer Initialisierer
Methode zwei: Instanzinitialisierer (anonyme Unterklasse) oder eine andere Methode?

Was sind die Vor- und Nachteile von jedem?

Hier ist ein Beispiel, das die beiden Methoden veranschaulicht:

import java.util.HashMap;
import java.util.Map;

public class Test {
    private static final Map<Integer, String> myMap = new HashMap<>();
    static {
        myMap.put(1, "one");
        myMap.put(2, "two");
    }

    private static final Map<Integer, String> myMap2 = new HashMap<>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}

2
Zum Initialisieren einer Karte in Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal

2
Verwenden Sie niemals die Initialisierung mit doppelter Klammer - dies ist ein Hack und eine einfache Möglichkeit, Speicher zu verlieren und andere Probleme zu verursachen.
dimo414

Java 9? Wenn Einträge <= 10 zählen, verwenden Sie Map.ofelse Map.ofEntries, überprüfen Sie stackoverflow.com/a/37384773/1216775
akhil_mittal

Antworten:


1106

Der Instanzinitialisierer ist in diesem Fall nur syntaktischer Zucker, oder? Ich verstehe nicht, warum Sie eine zusätzliche anonyme Klasse benötigen, um sie zu initialisieren. Und es wird nicht funktionieren, wenn die erstellte Klasse endgültig ist.

Sie können eine unveränderliche Karte auch mit einem statischen Initialisierer erstellen:

public class Test {
    private static final Map<Integer, String> myMap;
    static {
        Map<Integer, String> aMap = ....;
        aMap.put(1, "one");
        aMap.put(2, "two");
        myMap = Collections.unmodifiableMap(aMap);
    }
}

10
Dies ist die Redewendung, die ich seit Jahren verwende und bei der noch nie jemand ein Auge darauf geworfen hat. Dasselbe mache ich auch für nicht veränderbare konstante Mengen und Listen.
Jasonmp85

3
Wie würde ich mit einer HashMap <String, String> mit einem String-Schlüssel umgehen? Das Map-Objekt erlaubt mir nicht, einen String-Schlüssel zu haben, daher kann ich unmodizableMap () nicht verwenden. Ich denke, das Casting auf eine HashMap würde den Zweck ebenfalls zunichte machen. Irgendwelche Ideen?
Luke

30
@ Luke Ich bezweifle ernsthaft, dass Android eine solche Einschränkung hat. Es macht überhaupt keinen Sinn. Eine schnelle Suche hat diese Frage hier (und viele andere) gefunden, was zu bedeuten scheint, dass Sie einen String-Schlüssel für ein Map-Objekt in Android verwenden können.
mluisbrown

11
Damit sich niemand anders darum kümmert, kann ich bestätigen, dass es kein Problem gibt, einen String-Schlüssel für ein Map-Objekt unter Android zu verwenden.
Jordanien

11
Jordan: Es ist jetzt ein altes Thema, aber ich vermute, @Luke hat versucht, eine Zeichenfolge als Schlüssel in einer Karte zu verwenden, die einen anderen Schlüsseltyp hatte, z. B. Map <Integer, String>.
Elende Variable

445

Ich mag die Guave- Methode zum Initialisieren einer statischen, unveränderlichen Karte:

static final Map<Integer, String> MY_MAP = ImmutableMap.of(
    1, "one",
    2, "two"
);

Wie Sie sehen können, ist es sehr prägnant (aufgrund der praktischen Factory-Methoden in ImmutableMap).

Wenn die Karte mehr als 5 Einträge enthalten soll, können Sie sie nicht mehr verwenden ImmutableMap.of(). Versuchen Sie stattdessen Folgendes ImmutableMap.builder():

static final Map<Integer, String> MY_MAP = ImmutableMap.<Integer, String>builder()
    .put(1, "one")
    .put(2, "two")
    // ... 
    .put(15, "fifteen")
    .build();

Weitere Informationen zu den Vorteilen der Dienstprogramme für unveränderliche Sammlungen in Guava finden Sie unter Erläuterungen zu unveränderlichen Sammlungen im Guava-Benutzerhandbuch .

(Eine Untergruppe von) Guave hieß früher Google Collections . Wenn Sie diese Bibliothek noch nicht in Ihrem Java-Projekt verwenden, empfehle ich dringend , sie auszuprobieren! Guava hat sich schnell zu einer der beliebtesten und nützlichsten kostenlosen Bibliotheken von Drittanbietern für Java entwickelt, da andere SO-Benutzer zustimmen . (Wenn Sie neu darin sind, steckt hinter diesem Link einige hervorragende Lernressourcen.)


Update (2015) : Was Java 8 betrifft, würde ich immer noch den Guava-Ansatz verwenden, da er viel sauberer ist als alles andere. Wenn Sie keine Guava-Abhängigkeit wünschen, ziehen Sie eine einfache alte Init-Methode in Betracht . Der Hack mit zweidimensionalem Array und Stream-API ist ziemlich hässlich, wenn Sie mich fragen, und wird hässlicher, wenn Sie eine Map erstellen müssen, deren Schlüssel und Werte nicht vom gleichen Typ sind (wie Map<Integer, String>in der Frage).

Was die Zukunft von Guava im Allgemeinen in Bezug auf Java 8 betrifft, sagte Louis Wasserman dies bereits 2014, und [ Update ] 2016 wurde angekündigt, dass Guava 21 Java 8 benötigen und ordnungsgemäß unterstützen wird .


Update (2016) : Wie Tagir Valeev hervorhebt, wird Java 9 dies endlich sauber machen, indem nur reines JDK verwendet wird, indem praktische Factory-Methoden für Sammlungen hinzugefügt werden :

static final Map<Integer, String> MY_MAP = Map.of(
    1, "one", 
    2, "two"
);

21
Es scheint, als hätten unsere SO-Administratoren die ehrwürdige Frage "Die nützlichsten kostenlosen Java-Bibliotheken von Drittanbietern" gelöscht, auf die ich verlinkt habe. :( Verdammt sie.
Jonik

2
Ich stimme zu, dies ist der schönste Weg, eine konstante Karte zu initialisieren. Nicht nur besser lesbar, sondern auch, da Collections.unmodizableMap eine schreibgeschützte Ansicht der zugrunde liegenden Karte zurückgibt (die noch geändert werden kann).
Crunchdog

11
Ich kann jetzt gelöschte Fragen sehen (mit mehr als 10.000 Wiederholungen). Hier ist eine Kopie der 'nützlichsten kostenlosen Java-Bibliotheken von Drittanbietern' . Es ist nur die erste Seite, aber zumindest finden Sie die oben genannten Guava-Ressourcen .
Jonik

2
Ich bevorzuge diesen Ansatz wirklich, obwohl es von Vorteil ist, zu wissen, wie man es ohne zusätzliche Abhängigkeiten macht.
Schraubenschlüssel

2
JEP 186 ist immer noch nicht geschlossen, daher werden möglicherweise neue Funktionen im Zusammenhang mit Sammlungsliteralen eingeführt
Cybersoft

182

Ich würde ... benutzen:

public class Test {
    private static final Map<Integer, String> MY_MAP = createMap();

    private static Map<Integer, String> createMap() {
        Map<Integer, String> result = new HashMap<>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  1. Es vermeidet eine anonyme Klasse, die ich persönlich als schlechten Stil betrachte, und vermeidet sie
  2. Dadurch wird die Erstellung einer Karte expliziter
  3. es macht die Karte unveränderbar
  4. Da MY_MAP konstant ist, würde ich es als konstant bezeichnen

3
Von den reinen JDK-Optionen (keine Bibliotheken) gefällt mir dies am besten, da die Kartendefinition eindeutig mit ihrer Initialisierung verknüpft ist. Auch auf ständige Benennung vereinbart.
Jonik

Mir ist nie in den Sinn gekommen, dass Sie das tun könnten.
Romulusnr

181

Java 5 bietet diese kompaktere Syntax:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};

46
Diese Technik wird als Double-Brace-Initialisierung bezeichnet: stackoverflow.com/questions/1372113/… Es handelt sich nicht um eine spezielle Java 5-Syntax, sondern nur um einen Trick mit einer anonymen Klasse mit einem Instanzinitialisierer.
Jesper

13
Kurze Frage zur Initialisierung der doppelten Klammer: Dabei gibt Eclipse eine Warnung über eine fehlende Seriennummer aus. Einerseits verstehe ich nicht, warum in diesem speziellen Fall eine Seriennummer benötigt wird, andererseits mag ich es normalerweise nicht, Warnungen zu unterdrücken. Was denkst du darüber?
Nbarraille

8
@nbarraille Das liegt daran HashMap implements Serializable. Da Sie mit diesem "Trick" tatsächlich eine Unterklasse von HashMap erstellen, erstellen Sie implizit eine serialisierbare Klasse. Und dafür sollten Sie eine serialUID angeben.
niemand

5
Double brace initialization can cause memory leaks when used from a non-static context, because the anonymous class created will maintain a reference to the surrounding object. It has worse performance than regular initialization because of the additional class loading required. It can cause equals() comparisons to fail, if the equals() method does not accept subclasses as parameter. And finally, pre Java 9 it cannot be combined with the diamond operator, because that cannot be used with anonymous classes.- IntelliJ
Mark Jeronimus

3
@ MarkJeronimus - Die vorgeschlagene Verwendung ist ein statischer Kontext. Die Leistung kann schlechter sein, aber nicht merklich, wenn es sich um eine vermutlich kleine Anzahl statisch definierter Karten handelt. HashMap.equalsist in einer beliebigen Unterklasse von Map definiert AbstractMapund funktioniert in dieser , sodass dies hier kein Problem darstellt. Die Sache mit dem Diamantenoperator ist ärgerlich, wurde aber wie erwähnt jetzt behoben.
Jules

95

Ein Vorteil der zweiten Methode besteht darin, dass Sie sie umschließen können, um sicherzustellen Collections.unmodifiableMap(), dass die Sammlung später nicht mehr aktualisiert wird:

private static final Map<Integer, String> CONSTANT_MAP = 
    Collections.unmodifiableMap(new HashMap<Integer, String>() {{ 
        put(1, "one");
        put(2, "two");
    }});

 // later on...

 CONSTANT_MAP.put(3, "three"); // going to throw an exception!

3
Können Sie dies bei der ersten Methode nicht einfach tun, indem Sie den neuen Operator in den statischen {} Block verschieben und ihn einschließen?
Patrick

2
Ich würde den Konstruktoraufruf sowieso in die statische Initialisierung verschieben. Alles andere sieht nur seltsam aus.
Tom Hawtin - Tackline

2
Gibt es eine Vorstellung davon, welchen Leistungseinbruch die Verwendung einer anonymen Klasse im Gegensatz zu einer konkreten Klasse haben könnte?
Kip

62

Hier ist ein einzeiliger statischer Java 8-Karteninitialisierer:

private static final Map<String, String> EXTENSION_TO_MIMETYPE =
    Arrays.stream(new String[][] {
        { "txt", "text/plain" }, 
        { "html", "text/html" }, 
        { "js", "application/javascript" },
        { "css", "text/css" },
        { "xml", "application/xml" },
        { "png", "image/png" }, 
        { "gif", "image/gif" }, 
        { "jpg", "image/jpeg" },
        { "jpeg", "image/jpeg" }, 
        { "svg", "image/svg+xml" },
    }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1]));

Bearbeiten: Um ein Map<Integer, String>wie in der Frage zu initialisieren , benötigen Sie ungefähr Folgendes:

static final Map<Integer, String> MY_MAP = Arrays.stream(new Object[][]{
        {1, "one"},
        {2, "two"},
}).collect(Collectors.toMap(kv -> (Integer) kv[0], kv -> (String) kv[1]));

Bearbeiten (2): Es gibt eine bessere Version von i_am_zero, die für gemischte Typen geeignet ist und einen Anrufstrom verwendet new SimpleEntry<>(k, v). Überprüfen Sie diese Antwort: https://stackoverflow.com/a/37384773/3950982


7
Ich habe mir erlaubt, eine Version hinzuzufügen, die der Frage und anderen Antworten entspricht: Initiiere eine Map, deren Schlüssel und Werte unterschiedlichen Typs sind (also String[][]nicht, Object[][]wird benötigt). IMHO, dieser Ansatz ist hässlich (noch mehr bei den Darstellern) und schwer zu merken; würde es nicht selbst benutzen.
Jonik

57

Map.of in Java 9+

private static final Map<Integer, String> MY_MAP = Map.of(1, "one", 2, "two");

Siehe JEP 269 für Details. JDK 9 erreichte im September 2017 die allgemeine Verfügbarkeit .


7
Oder wenn Sie mehr als 10 Schlüssel-Wert-Paare möchten, können Sie verwendenMap.ofEntries
ZhekaKozlov

8
Dies ist sauber und alles, bis Sie erkennen, wie es implementiert wurde
Mitte

Ugh das ist so traurig - sieht so aus, als ob es nur 10 Einträge unterstützt, nach denen Sie ofEntries verwenden müssen. Lame.
Somaiah Kumbera

2
Die Sauberkeit der Implementierung im JDK sollte keine Rolle spielen, solange es funktioniert und den Vertrag erfüllt. Wie bei jeder Black Box können Implementierungsdetails in Zukunft
jederzeit korrigiert

@mid Dies ist die einzige typsichere Möglichkeit, dies in Java zu tun.
Luke Hutchison

44

Java 9

Wir können Map.ofEntriesCall verwenden Map.entry( k , v ), um jeden Eintrag zu erstellen.

import static java.util.Map.entry;
private static final Map<Integer,String> map = Map.ofEntries(
        entry(1, "one"),
        entry(2, "two"),
        entry(3, "three"),
        entry(4, "four"),
        entry(5, "five"),
        entry(6, "six"),
        entry(7, "seven"),
        entry(8, "eight"),
        entry(9, "nine"),
        entry(10, "ten"));

Wir können auch verwenden, Map.ofwie von Tagir in seiner Antwort hier vorgeschlagen, aber wir können nicht mehr als 10 Einträge verwenden Map.of.

Java 8 (ordentliche Lösung)

Wir können einen Stream von Karteneinträgen erstellen. Wir haben bereits zwei Implementierungen Entryin java.util.AbstractMapdenen SimpleEntry und SimpleImmutableEntry . Für dieses Beispiel können wir erstere verwenden als:

import java.util.AbstractMap.*;
private static final Map<Integer, String> myMap = Stream.of(
            new SimpleEntry<>(1, "one"),
            new SimpleEntry<>(2, "two"),
            new SimpleEntry<>(3, "three"),
            new SimpleEntry<>(4, "four"),
            new SimpleEntry<>(5, "five"),
            new SimpleEntry<>(6, "six"),
            new SimpleEntry<>(7, "seven"),
            new SimpleEntry<>(8, "eight"),
            new SimpleEntry<>(9, "nine"),
            new SimpleEntry<>(10, "ten"))
            .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));

2
Der new SimpleEntry<>()Weg ist weit weniger lesbar als statisch put(): /
Danon

32

Mit Eclipse Collections funktionieren alle folgenden Funktionen:

import java.util.Map;

import org.eclipse.collections.api.map.ImmutableMap;
import org.eclipse.collections.api.map.MutableMap;
import org.eclipse.collections.impl.factory.Maps;

public class StaticMapsTest
{
    private static final Map<Integer, String> MAP =
        Maps.mutable.with(1, "one", 2, "two");

    private static final MutableMap<Integer, String> MUTABLE_MAP =
       Maps.mutable.with(1, "one", 2, "two");


    private static final MutableMap<Integer, String> UNMODIFIABLE_MAP =
        Maps.mutable.with(1, "one", 2, "two").asUnmodifiable();


    private static final MutableMap<Integer, String> SYNCHRONIZED_MAP =
        Maps.mutable.with(1, "one", 2, "two").asSynchronized();


    private static final ImmutableMap<Integer, String> IMMUTABLE_MAP =
        Maps.mutable.with(1, "one", 2, "two").toImmutable();


    private static final ImmutableMap<Integer, String> IMMUTABLE_MAP2 =
        Maps.immutable.with(1, "one", 2, "two");
}

Sie können primitive Karten auch statisch mit Eclipse-Sammlungen initialisieren.

import org.eclipse.collections.api.map.primitive.ImmutableIntObjectMap;
import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
import org.eclipse.collections.impl.factory.primitive.IntObjectMaps;

public class StaticPrimitiveMapsTest
{
    private static final MutableIntObjectMap<String> MUTABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two");

    private static final MutableIntObjectMap<String> UNMODIFIABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .asUnmodifiable();

    private static final MutableIntObjectMap<String> SYNCHRONIZED_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .asSynchronized();

    private static final ImmutableIntObjectMap<String> IMMUTABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .toImmutable();

    private static final ImmutableIntObjectMap<String> IMMUTABLE_INT_OBJ_MAP2 =
            IntObjectMaps.immutable.<String>empty()
                    .newWithKeyValue(1, "one")
                    .newWithKeyValue(2, "two");
} 

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen


1
Ich wünschte wirklich, Eclipse Collections wäre die Standard-Sammlungsbibliothek für Java. Ich genieße es so viel mehr als Guava + JCL.
Kenny Cason

29

Ich würde in dieser Situation niemals eine anonyme Unterklasse erstellen. Statische Initialisierer funktionieren genauso gut, wenn Sie die Karte beispielsweise nicht ändern möchten:

private static final Map<Integer, String> MY_MAP;
static
{
    Map<Integer, String>tempMap = new HashMap<Integer, String>();
    tempMap.put(1, "one");
    tempMap.put(2, "two");
    MY_MAP = Collections.unmodifiableMap(tempMap);
}

1
In welcher Situation würden Sie dann eine anonyme Unterklasse verwenden, um eine Hashmap zu initialisieren?
Dogbane

6
Niemals eine Sammlung initialisieren.
Eljenso

Können Sie erklären, warum die Verwendung eines statischen Initialisierers die bessere Wahl ist als die Erstellung einer anonymen Unterklasse?
Leba-Lev

3
@rookie In anderen Antworten werden mehrere Gründe für die statische Init angegeben. Das Ziel hier ist die Initialisierung. Warum also die Unterklasse einfügen, außer vielleicht ein paar Tastenanschläge zu speichern? (Wenn Sie Tastenanschläge sparen möchten, ist Java als Programmiersprache definitiv keine gute Wahl.) Eine Faustregel, die ich beim Programmieren in Java verwende, lautet: Unterklasse so wenig wie möglich (und niemals, wenn dies vernünftigerweise vermieden werden kann).
Eljenso

@eljenso - der Grund, warum ich generell die Unterklassensyntax dafür bevorzuge, ist, dass sie die Initialisierung inline setzt, wo sie hingehört . Eine zweitbeste Wahl ist das Aufrufen einer statischen Methode, die die initialisierte Karte zurückgibt. Aber ich fürchte, ich würde mir Ihren Code ansehen und ein paar Sekunden damit verbringen, herauszufinden, woher MY_MAP kommt, und das ist Zeit, die ich nicht verschwenden möchte. Jede Verbesserung der Lesbarkeit ist ein Bonus, und die Konsequenzen für die Leistung sind minimal. Daher scheint es mir die beste Option zu sein.
Jules

18

Vielleicht ist es interessant, sich Google Collections anzusehen , z. B. die Videos, die sie auf ihrer Seite haben. Sie bieten verschiedene Möglichkeiten zum Initialisieren von Karten und Sets sowie unveränderliche Sammlungen.

Update: Diese Bibliothek heißt jetzt Guava .


17

Ich mag anonymen Unterricht, weil es einfach ist, damit umzugehen:

public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
    {
        put(1, "some value");
                    //rest of code here
    }
});

12
public class Test {
    private static final Map<Integer, String> myMap;
    static {
        Map<Integer, String> aMap = ....;
        aMap.put(1, "one");
        aMap.put(2, "two");
        myMap = Collections.unmodifiableMap(aMap);
    }
}

Wenn wir mehr als eine Konstante deklarieren, wird dieser Code in einen statischen Block geschrieben und ist in Zukunft schwer zu pflegen. Es ist also besser, eine anonyme Klasse zu verwenden.

public class Test {

    public static final Map numbers = Collections.unmodifiableMap(new HashMap(2, 1.0f){
        {
            put(1, "one");
            put(2, "two");
        }
    });
}

Es wird empfohlen, unmodizableMap für Konstanten zu verwenden, andernfalls kann es nicht als Konstante behandelt werden.


10

Ich könnte den Stil der "doppelten Klammerinitialisierung" gegenüber dem statischen Blockstil empfehlen.

Jemand kann kommentieren, dass er anonyme Klasse, Overhead, Leistung usw. nicht mag.

Was ich jedoch mehr berücksichtige, ist die Lesbarkeit und Wartbarkeit des Codes. Unter diesem Gesichtspunkt halte ich eine doppelte Klammer für einen besseren Codestil als eine statische Methode.

  1. Die Elemente sind verschachtelt und inline.
  2. Es ist mehr OO, nicht prozedural.
  3. Die Auswirkungen auf die Leistung sind sehr gering und können ignoriert werden.
  4. Bessere Unterstützung für IDE-Gliederungen (eher als viele anonyme statische {} Blöcke)
  5. Sie haben einige Kommentarzeilen gespeichert, um eine Beziehung herzustellen.
  6. Verhindern Sie, dass ein mögliches Elementleck / eine Instanzleitung eines nicht initialisierten Objekts von der Ausnahme und dem Bytecode-Optimierer abweicht.
  7. Keine Sorge um die Reihenfolge der Ausführung des statischen Blocks.

Wenn Sie den GC der anonymen Klasse kennen, können Sie ihn jederzeit mithilfe von in eine normale HashMap konvertieren new HashMap(Map map).

Sie können dies tun, bis Sie auf ein anderes Problem stoßen. Wenn Sie dies tun, sollten Sie dafür einen anderen Codierungsstil (z. B. keine statische, Factory-Klasse) verwenden.


8

Wie üblich hat Apache-Commons die richtige Methode MapUtils.putAll (Map, Object []) :

So erstellen Sie eine Farbkarte:

Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
     {"RED", "#FF0000"},
     {"GREEN", "#00FF00"},
     {"BLUE", "#0000FF"}
 });

Ich beziehe Apache Commons in alle Builds ein. Arrays.asMap( ... )Da leider keine Methode in einfachem Java vorhanden ist, halte ich dies für die beste Lösung. Das Rad neu zu erfinden ist normalerweise albern. Ein sehr kleiner Nachteil ist, dass bei Generika eine ungeprüfte Konvertierung erforderlich ist.
Mike Nagetier

@mikerodent 4.1 Version ist generisch: public static <K, V> Map <K, V> putAll (endgültige Map <K, V> Map, endgültiges Object [] Array)
agad

Tx ... ja, ich benutze 4.1, aber ich muss noch SuppressWarnings( unchecked )in Eclipse mit einer Linie wieMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
Mike Nagetier

@mikerodent ist es nicht wegen Object [] [] ? Siehe aktualisierte Abnutzung - Ich habe keine Warnung in Eclipse.
Agad

Wie seltsam ... selbst wenn ich gehe, String[][]bekomme ich die "Warnung"! Und das funktioniert natürlich nur, wenn Sie Kund Vdie gleiche Klasse sind. Ich nehme an, Sie haben in Ihrem Eclipse-Setup (verständlicherweise) "ungeprüfte Konvertierung" nicht auf "Ignorieren" gesetzt.
Mike Nagetier

7

Hier ist mein Favorit, wenn ich Guaven nicht verwenden möchte (oder kann) ImmutableMap.of()oder wenn ich eine veränderbare brauche Map:

public static <A> Map<String, A> asMap(Object... keysAndValues) {
    return new LinkedHashMap<String, A>() {{
        for (int i = 0; i < keysAndValues.length - 1; i++) {
            put(keysAndValues[i].toString(), (A) keysAndValues[++i]);
        }
    }};
}

Es ist sehr kompakt und ignoriert Streuwerte (dh einen endgültigen Schlüssel ohne Wert).

Verwendungszweck:

Map<String, String> one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal");
Map<String, Object> two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2));

7

Wenn Sie eine nicht modifizierbare Karte möchten, hat Java 9 schließlich eine coole Factory-Methode ofzur MapSchnittstelle hinzugefügt . Eine ähnliche Methode wird auch zu Set, List hinzugefügt.

Map<String, String> unmodifiableMap = Map.of("key1", "value1", "key2", "value2");


6

Ich bevorzuge die Verwendung eines statischen Initialisierers, um zu vermeiden, dass anonyme Klassen generiert werden (was keinen weiteren Zweck hätte). Daher werde ich Tipps auflisten, die mit einem statischen Initialisierer initialisiert werden. Alle aufgeführten Lösungen / Tipps sind typsicher.

Hinweis: Die Frage sagt nichts darüber aus, wie die Karte nicht geändert werden kann, daher werde ich das weglassen, aber ich weiß, dass dies leicht möglich ist Collections.unmodifiableMap(map).

Erster Tipp

Der erste Tipp ist, dass Sie einen lokalen Verweis auf die Karte erstellen und ihr einen KURZEN Namen geben können:

private static final Map<Integer, String> myMap = new HashMap<>();
static {
    final Map<Integer, String> m = myMap; // Use short name!
    m.put(1, "one"); // Here referencing the local variable which is also faster!
    m.put(2, "two");
    m.put(3, "three");
}

Zweiter Tipp

Der zweite Tipp ist, dass Sie eine Hilfsmethode zum Hinzufügen von Einträgen erstellen können. Sie können diese Hilfsmethode auch öffentlich machen, wenn Sie:

private static final Map<Integer, String> myMap2 = new HashMap<>();
static {
    p(1, "one"); // Calling the helper method.
    p(2, "two");
    p(3, "three");
}

private static void p(Integer k, String v) {
    myMap2.put(k, v);
}

Die Hilfsmethode kann hier jedoch nicht wiederverwendet werden, da nur Elemente hinzugefügt werden können myMap2. Um es wiederverwendbar zu machen, könnten wir die Karte selbst zu einem Parameter der Hilfsmethode machen, aber dann wäre der Initialisierungscode nicht kürzer.

Dritter Tipp

Der dritte Tipp ist, dass Sie mit der Auffüllfunktion eine wiederverwendbare Builder-ähnliche Hilfsklasse erstellen können. Dies ist wirklich eine einfache Helferklasse mit 10 Zeilen, die typsicher ist:

public class Test {
    private static final Map<Integer, String> myMap3 = new HashMap<>();
    static {
        new B<>(myMap3)   // Instantiating the helper class with our map
            .p(1, "one")
            .p(2, "two")
            .p(3, "three");
    }
}

class B<K, V> {
    private final Map<K, V> m;

    public B(Map<K, V> m) {
        this.m = m;
    }

    public B<K, V> p(K k, V v) {
        m.put(k, v);
        return this; // Return this for chaining
    }
}

5

Die anonyme Klasse, die Sie erstellen, funktioniert gut. Sie sollten sich jedoch bewusst sein, dass dies eine innere Klasse ist und als solche einen Verweis auf die umgebende Klasseninstanz enthält. Sie werden also feststellen, dass Sie bestimmte Dinge damit nicht tun können (mit XStream für einen). Sie werden einige sehr seltsame Fehler bekommen.

Abgesehen davon ist dieser Ansatz in Ordnung, solange Sie sich dessen bewusst sind. Ich benutze es die meiste Zeit, um alle Arten von Sammlungen auf prägnante Weise zu initialisieren.

BEARBEITEN: In den Kommentaren wurde korrekt darauf hingewiesen, dass dies eine statische Klasse ist. Offensichtlich habe ich das nicht genau genug gelesen. Meine Kommentare gelten jedoch weiterhin für anonyme innere Klassen.


3
In diesem speziellen Fall ist es statisch, also keine äußere Instanz.
Tom Hawtin - Tackline

Wahrscheinlich sollte XStream nicht versuchen, solche Dinge zu serialisieren (es ist statisch. Warum sollten Sie eine statische Variable serialisieren müssen?)
jasonmp85

5

Wenn Sie etwas knappes und relativ sicheres möchten, können Sie die Typprüfung zur Kompilierungszeit einfach auf die Laufzeit verschieben:

static final Map<String, Integer> map = MapUtils.unmodifiableMap(
    String.class, Integer.class,
    "cat",  4,
    "dog",  2,
    "frog", 17
);

Diese Implementierung sollte alle Fehler auffangen:

import java.util.HashMap;

public abstract class MapUtils
{
    private MapUtils() { }

    public static <K, V> HashMap<K, V> unmodifiableMap(
            Class<? extends K> keyClazz,
            Class<? extends V> valClazz,
            Object...keyValues)
    {
        return Collections.<K, V>unmodifiableMap(makeMap(
            keyClazz,
            valClazz,
            keyValues));
    }

    public static <K, V> HashMap<K, V> makeMap(
            Class<? extends K> keyClazz,
            Class<? extends V> valClazz,
            Object...keyValues)
    {
        if (keyValues.length % 2 != 0)
        {
            throw new IllegalArgumentException(
                    "'keyValues' was formatted incorrectly!  "
                  + "(Expected an even length, but found '" + keyValues.length + "')");
        }

        HashMap<K, V> result = new HashMap<K, V>(keyValues.length / 2);

        for (int i = 0; i < keyValues.length;)
        {
            K key = cast(keyClazz, keyValues[i], i);
            ++i;
            V val = cast(valClazz, keyValues[i], i);
            ++i;
            result.put(key, val);
        }

        return result;
    }

    private static <T> T cast(Class<? extends T> clazz, Object object, int i)
    {
        try
        {
            return clazz.cast(object);
        }
        catch (ClassCastException e)
        {
            String objectName = (i % 2 == 0) ? "Key" : "Value";
            String format = "%s at index %d ('%s') wasn't assignable to type '%s'";
            throw new IllegalArgumentException(String.format(format, objectName, i, object.toString(), clazz.getSimpleName()), e);
        }
    }
}

4

Mit Java 8 verwende ich das folgende Muster:

private static final Map<String, Integer> MAP = Stream.of(
    new AbstractMap.SimpleImmutableEntry<>("key1", 1),
    new AbstractMap.SimpleImmutableEntry<>("key2", 2)
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Es ist nicht der knappste und etwas umständlichste, aber

  • es erfordert nichts außerhalb von java.util
  • Es ist typsicher und bietet Platz für verschiedene Typen für Schlüssel und Wert.

Bei Bedarf kann die toMapSignatur einschließlich eines Kartenanbieters verwendet werden, um den Kartentyp anzugeben.
Zrvan



4

Ihr zweiter Ansatz (Double Brace-Initialisierung) wird als Anti-Pattern angesehen , daher würde ich mich für den ersten Ansatz entscheiden.

Eine andere einfache Möglichkeit, eine statische Karte zu initialisieren, ist die Verwendung dieser Dienstprogrammfunktion:

public static <K, V> Map<K, V> mapOf(Object... keyValues) {
    Map<K, V> map = new HashMap<>(keyValues.length / 2);

    for (int index = 0; index < keyValues.length / 2; index++) {
        map.put((K)keyValues[index * 2], (V)keyValues[index * 2 + 1]);
    }

    return map;
}

Map<Integer, String> map1 = mapOf(1, "value1", 2, "value2");
Map<String, String> map2 = mapOf("key1", "value1", "key2", "value2");

Hinweis: In können Java 9Sie Map.of verwenden


3

Ich mag die Syntax des statischen Initialisierers nicht und bin von anonymen Unterklassen nicht überzeugt. Im Allgemeinen stimme ich allen Nachteilen der Verwendung statischer Initialisierer und allen Nachteilen der Verwendung anonymer Unterklassen zu, die in den vorherigen Antworten erwähnt wurden. Andererseits reichen mir die in diesen Beiträgen vorgestellten Profis nicht aus. Ich bevorzuge die statische Initialisierungsmethode:

public class MyClass {
    private static final Map<Integer, String> myMap = prepareMap();

    private static Map<Integer, String> prepareMap() {
        Map<Integer, String> hashMap = new HashMap<>();
        hashMap.put(1, "one");
        hashMap.put(2, "two");

        return hashMap;
    }
}

3

Ich habe den Ansatz, den ich verwende (und der mir immer besser gefällt), in keiner Antwort gesehen. Hier ist er also:

Ich mag es nicht, statische Initialisierer zu verwenden, weil sie klobig sind, und ich mag keine anonymen Klassen, weil sie für jede Instanz eine neue Klasse erstellen.

Stattdessen bevorzuge ich eine Initialisierung, die folgendermaßen aussieht:

map(
    entry("keyA", "val1"),
    entry("keyB", "val2"),
    entry("keyC", "val3")
);

Leider sind diese Methoden nicht Teil der Standard-Java-Bibliothek. Daher müssen Sie eine Dienstprogrammbibliothek erstellen (oder verwenden), die die folgenden Methoden definiert:

 public static <K,V> Map<K,V> map(Map.Entry<K, ? extends V>... entries)
 public static <K,V> Map.Entry<K,V> entry(K key, V val)

(Sie können 'statisch importieren' verwenden, um zu vermeiden, dass der Name der Methode vorangestellt werden muss.)

Ich fand es nützlich, ähnliche statische Methoden für die anderen Sammlungen (list, set, sortedSet, sortedMap usw.) bereitzustellen.

Es ist nicht ganz so schön wie die Initialisierung von JSON-Objekten, aber es ist ein Schritt in diese Richtung, was die Lesbarkeit betrifft.


3

Da Java keine Kartenliterale unterstützt, müssen Karteninstanzen immer explizit instanziiert und gefüllt werden.

Glücklicherweise ist es möglich, das Verhalten von Kartenliteralen in Java mithilfe von Factory-Methoden zu approximieren .

Zum Beispiel:

public class LiteralMapFactory {

    // Creates a map from a list of entries
    @SafeVarargs
    public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
        LinkedHashMap<K, V> map = new LinkedHashMap<>();
        for (Map.Entry<K, V> entry : entries) {
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }
    // Creates a map entry
    public static <K, V> Map.Entry<K, V> entry(K key, V value) {
        return new AbstractMap.SimpleEntry<>(key, value);
    }

    public static void main(String[] args) {
        System.out.println(mapOf(entry("a", 1), entry("b", 2), entry("c", 3)));
    }
}

Ausgabe:

{a = 1, b = 2, c = 3}

Es ist viel praktischer, als die Karte einzeln zu erstellen und zu füllen.


2

JEP 269 bietet einige praktische Factory-Methoden für die Sammlungs-API. Diese Factory-Methoden sind nicht in der aktuellen Java-Version 8 enthalten, sondern für die Java 9-Version geplant.

Denn Mapes gibt zwei Fabrikmethoden: ofund ofEntries. Mit ofkönnen Sie abwechselnd Schlüssel / Wert-Paare übergeben. Zum Beispiel, um ein MapLike zu erstellen {age: 27, major: cs}:

Map<String, Object> info = Map.of("age", 27, "major", "cs");

Derzeit gibt es zehn überladene Versionen für of, sodass Sie eine Karte mit zehn Schlüssel / Wert-Paaren erstellen können. Wenn Ihnen diese Einschränkung oder alternative Schlüssel / Werte nicht gefallen, können Sie Folgendes verwenden ofEntries:

Map<String, Object> info = Map.ofEntries(
                Map.entry("age", 27),
                Map.entry("major", "cs")
);

Beide ofund ofEntriesgeben eine unveränderliche zurück Map, so dass Sie ihre Elemente nach der Konstruktion nicht ändern können. Sie können diese Funktionen mit JDK 9 Early Access ausprobieren .


2

Nun ... ich mag Aufzählungen;)

enum MyEnum {
    ONE   (1, "one"),
    TWO   (2, "two"),
    THREE (3, "three");

    int value;
    String name;

    MyEnum(int value, String name) {
        this.value = value;
        this.name = name;
    }

    static final Map<Integer, String> MAP = Stream.of( values() )
            .collect( Collectors.toMap( e -> e.value, e -> e.name ) );
}

2

Ich habe die Antworten gelesen und beschlossen, meinen eigenen Kartenersteller zu schreiben. Fühlen Sie sich frei zu kopieren, einzufügen und zu genießen.

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * A tool for easy creation of a map. Code example:<br/>
 * {@code MapBuilder.of("name", "Forrest").and("surname", "Gump").build()}
 * @param <K> key type (inferred by constructor)
 * @param <V> value type (inferred by constructor)
 * @author Vlasec (for http://stackoverflow.com/a/30345279/1977151)
 */
public class MapBuilder <K, V> {
    private Map<K, V> map = new HashMap<>();

    /** Constructor that also enters the first entry. */
    private MapBuilder(K key, V value) {
        and(key, value);
    }

    /** Factory method that creates the builder and enters the first entry. */
    public static <A, B> MapBuilder<A, B> mapOf(A key, B value) {
        return new MapBuilder<>(key, value);
    }

    /** Puts the key-value pair to the map and returns itself for method chaining */
    public MapBuilder<K, V> and(K key, V value) {
        map.put(key, value);
        return this;
    }

    /**
     * If no reference to builder is kept and both the key and value types are immutable,
     * the resulting map is immutable.
     * @return contents of MapBuilder as an unmodifiable map.
     */
    public Map<K, V> build() {
        return Collections.unmodifiableMap(map);
    }
}

EDIT: In letzter Zeit finde ich ofziemlich oft öffentliche statische Methoden und ich mag es irgendwie. Ich fügte es dem Code hinzu und machte den Konstruktor privat, wodurch ich zum statischen Factory-Methodenmuster wechselte.

EDIT2: In jüngerer Zeit mag ich die aufgerufene statische Methode nicht mehr of, da sie bei der Verwendung statischer Importe ziemlich schlecht aussieht. Ich habe es mapOfstattdessen in umbenannt, um es für statische Importe besser geeignet zu machen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.