Come posso inizializzare una mappa statica?


1132

Come inizializzeresti una statica Mapin Java?

Metodo 1: inizializzatore statico
Metodo 2: inizializzatore di istanza (sottoclasse anonima) o qualche altro metodo?

Quali sono i pro ed i contro di ognuno?

Ecco un esempio che illustra i due metodi:

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
Per l'inizializzazione di una mappa in Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal

2
Per favore, non usare mai l' inizializzazione a doppia parentesi graffa : è un hack e un modo semplice per perdere memoria e causare altri problemi.
dimo414,

Java 9? Se le voci count <= 10 utilizzo Map.ofaltro Map.ofEntries, controllare stackoverflow.com/a/37384773/1216775
akhil_mittal

Risposte:


1106

L'inizializzatore dell'istanza è solo zucchero sintattico in questo caso, giusto? Non vedo perché hai bisogno di una lezione extra anonima solo per inizializzare. E non funzionerà se la classe creata è definitiva.

Puoi creare una mappa immutabile anche usando un inizializzatore statico:

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
Questo è il linguaggio che uso da anni e non ho mai avuto nessuno che lo guardasse. Faccio lo stesso anche per set ed elenchi costanti non modificabili.
jasonmp85,

3
Come gestire una HashMap <String, String> con una chiave String. L'oggetto Map non mi consente di avere una chiave String, quindi non posso usare unmodifiableMap (). Immagino che il casting su una HashMap avrebbe vanificato anche lo scopo. Qualche idea?
Luca,

30
@Luke Dubito seriamente che Android abbia una tale limitazione. Non ha per niente senso. Una rapida ricerca ha trovato questa domanda qui (e molte altre) che sembra implicare che puoi usare una chiave String per un oggetto Map in Android.
mluisbrown,

11
Quindi nessun altro si preoccupa di indagare, posso confermare che non c'è nessun problema con l'uso di una chiave String per un oggetto Map su Android.
Giordania,

11
Giordania: ora è un vecchio argomento ma sospetto che @Luke stesse cercando di usare una stringa come chiave in una mappa che aveva un tipo di chiave diverso, ad esempio Mappa <Numero intero, Stringa>.
Miserabile variabile

445

Mi piace il modo Guava di inizializzare una mappa statica e immutabile:

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

Come puoi vedere, è molto conciso (a causa dei convenienti metodi di fabbrica in ImmutableMap).

Se si desidera che la mappa contenga più di 5 voci, non è più possibile utilizzare ImmutableMap.of(). Invece, prova ImmutableMap.builder()seguendo queste linee:

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

Per ulteriori informazioni sui vantaggi delle utilità di raccolta immutabili di Guava, vedere Raccolte di immutabili spiegate nella Guida per l'utente di Guava .

(Un sottoinsieme di) Guava si chiamava Google Collections . Se non stai ancora utilizzando questa libreria nel tuo progetto Java, ti consiglio vivamente di provarlo! Guava è rapidamente diventato una delle librerie di terze parti gratuite più popolari e utili per Java, come concordano gli altri utenti SO . (Se sei nuovo ad esso, ci sono alcune eccellenti risorse di apprendimento dietro quel link.)


Aggiornamento (2015) : per quanto riguarda Java 8 , utilizzerei comunque l'approccio Guava perché è molto più pulito di ogni altra cosa. Se non vuoi la dipendenza da Guava, prendi in considerazione un vecchio metodo init semplice . L'hacking con array bidimensionale e API Stream è piuttosto brutto se me lo chiedi e diventa più brutto se devi creare una mappa le cui chiavi e valori non sono dello stesso tipo (come Map<Integer, String>nella domanda).

Per quanto riguarda il futuro di Guava in generale, per quanto riguarda Java 8, Louis Wasserman lo ha affermato nel 2014 e [ aggiornamento ] nel 2016 è stato annunciato che Guava 21 richiederà e supporterà correttamente Java 8 .


Aggiornamento (2016) : Come sottolinea Tagir Valeev , Java 9 renderà finalmente tutto pulito usando nient'altro che JDK puro, aggiungendo comodi metodi di fabbrica per le raccolte:

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

21
Sembra che i nostri colleghi amministratori SO abbiano eliminato la venerabile domanda "Le librerie Java di terze parti gratuite più utili" a cui ho collegato. :( Accidenti a loro.
Jonik,

2
Sono d'accordo, questo è il modo migliore per inizializzare una mappa costante. Non solo più leggibile ma anche dal momento che Collections.unmodifiableMap restituisce una vista di sola lettura della mappa sottostante (che può ancora essere modificata).
Crunchdog,

11
Ora posso vedere le domande cancellate (con 10k + rep), quindi ecco una copia delle "Librerie Java gratuite di terze parti più utili" . È solo la prima pagina, ma almeno puoi trovare le risorse di Guava sopra menzionate.
Jonik,

2
Preferisco davvero questo approccio, anche se è utile sapere come farlo senza dipendenze extra.
Chiave inglese del

2
JEP 186 non è ancora chiuso, quindi potrebbe introdurre nuove funzionalità relative ai letterali delle raccolte
cybersoft,

182

Io userei:

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. evita una classe anonima, che personalmente considero un cattivo stile, ed evito
  2. rende più esplicita la creazione della mappa
  3. rende la mappa immodificabile
  4. dato che MY_MAP è costante, lo chiamerei costante

3
Delle opzioni JDK pure (senza librerie), questa mi piace di più, perché la definizione della mappa è chiaramente collegata alla sua inizializzazione. Anche concordato sulla denominazione costante.
Jonik,

Non mi è mai venuto in mente che potresti farlo.
romulusnr

181

Java 5 fornisce questa sintassi più compatta:

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

46
Quella tecnica si chiama inizializzazione con doppio controvento: stackoverflow.com/questions/1372113/… Non è una sintassi Java 5 speciale, è solo un trucco con una classe anonima con un inizializzatore di istanza.
Jesper,

13
Domanda rapida relativa all'inizializzazione della doppia parentesi: durante questa operazione, Eclipse emette un avviso relativo a un ID seriale mancante. Da un lato, non vedo perché un ID seriale sarebbe necessario in questo caso specifico, ma dall'altro lato, di solito non mi piace sopprimere gli avvisi. Cosa ne pensi di questo?
nbarraille,

8
@nbarraille Ecco perché HashMap implements Serializable. Dato che in realtà crei una sottoclasse di HashMap usando questo "trucco", stai implicitamente creando una classe serializzabile. E per questo dovresti fornire un serialUID.
nessuno

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 - L'uso suggerito è un contesto statico. Le prestazioni potrebbero essere peggiori, ma non in modo evidente quando si tratta di un numero presumibilmente piccolo di mappe definite staticamente. HashMap.equalsè definito AbstractMape funziona su qualsiasi sottoclasse di Map, quindi non è un problema qui. La cosa dell'operatore del diamante è fastidiosa, ma come accennato ora è stato risolto.
Jules il

95

Un vantaggio del secondo metodo è che puoi avvolgerlo Collections.unmodifiableMap()per garantire che nulla aggiornerà la raccolta in un secondo momento:

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
Non puoi farlo facilmente nel primo metodo spostando il nuovo operatore nel blocco {} statico e avvolgendolo?
Patrick,

2
Sposterei comunque la chiamata del costruttore nell'inizializzazione statica. Nient'altro sembra strano.
Tom Hawtin - tackline

2
hai idea di quale potrebbe essere la performance colpita dall'uso di una classe anonima invece di una classe concreta?
Kip

62

Ecco un inizializzatore di mappe statiche a una riga Java 8:

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

Modifica: per inizializzare un Map<Integer, String>come nella domanda, avresti bisogno di qualcosa del genere:

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

Modifica (2): esiste una versione migliore e di tipo misto di i_am_zero che utilizza un flusso di new SimpleEntry<>(k, v)chiamate. Dai un'occhiata a questa risposta: https://stackoverflow.com/a/37384773/3950982


7
Mi sono preso la libertà di aggiungere una versione equivalente alla domanda e ad altre risposte: init una mappa le cui chiavi e valori sono di tipo diverso (quindi String[][]non Object[][]è necessario , è necessario). IMHO, questo approccio è brutto (ancora di più con i cast) e difficile da ricordare; non lo userei da solo.
Jonik,

57

Map.of in Java 9+

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

Vedere JEP 269 per i dettagli. JDK 9 ha raggiunto la disponibilità generale a settembre 2017.


7
O se vuoi più di 10 coppie chiave-valore, puoi usareMap.ofEntries
ZhekaKozlov,

8
Questo è pulito e tutto, fino a quando non ti rendi conto di come è stato implementato
metà

Questo è così triste - sembra che supporti solo 10 voci, dopo di che è necessario utilizzare le voci. Noioso.
Somaiah Kumbera,

2
La pulizia dell'implementazione nel JDK non dovrebbe avere importanza finché funziona e soddisfa il contratto. Come ogni scatola nera, i dettagli di implementazione possono sempre essere riparati in futuro, se davvero necessari ...
vikingsteve

@mid Questo è l'unico modo per fare questo in Java.
Luke Hutchison,

44

Java 9

Possiamo usare Map.ofEntries, chiamando Map.entry( k , v )per creare ogni voce.

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

Possiamo anche usare Map.ofcome suggerito da Tagir nella sua risposta qui, ma non possiamo avere più di 10 voci usando Map.of.

Java 8 (Neat Solution)

Possiamo creare un flusso di voci della mappa. Abbiamo già due implementazioni di Entryin java.util.AbstractMapcui sono SimpleEntry e SimpleImmutableEntry . Per questo esempio possiamo usare ex come:

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
La new SimpleEntry<>()via è molto meno leggibile di statica put(): /
Danon

32

Con le raccolte Eclipse , tutto quanto segue funzionerà:

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

Puoi anche inizializzare staticamente le mappe primitive con le raccolte Eclipse.

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

Nota: sono un committer per le raccolte Eclipse


1
Vorrei davvero che Eclipse Collections fosse la libreria di raccolte predefinita per Java. Mi piace molto di più di Guava + JCL.
Kenny Cason,

29

Non creerei mai una sottoclasse anonima in questa situazione. Gli inizializzatori statici funzionano ugualmente bene, ad esempio se si desidera rendere la mappa non modificabile:

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 quale situazione useresti quindi una sottoclasse anonima per inizializzare una hashmap?
dogbane,

6
Non inizializzare mai una raccolta.
eljenso,

Potresti spiegare perché usare un inizializzatore statico è una scelta migliore rispetto alla creazione di una sottoclasse anonima?
leba-lev,

3
@rookie Ci sono diverse ragioni fornite in altre risposte a favore dell'init statico. L'obiettivo qui è l'inizializzazione, quindi perché portare la sottoclasse, tranne forse per salvare qualche sequenza di tasti? (Se si desidera salvare le sequenze di tasti, Java non è sicuramente una buona scelta come linguaggio di programmazione.) Una regola empirica che uso quando si programma in Java è: sottoclasse il meno possibile (e mai quando può essere ragionevolmente evitato).
eljenso,

@eljenso - il motivo per cui in genere preferisco la sintassi della sottoclasse per questo è che mette in linea l'inizializzazione, a cui appartiene . Una seconda scelta è quella di chiamare un metodo statico che restituisce la mappa inizializzata. Ma temo di guardare il tuo codice e di dedicare qualche secondo a capire da dove proviene MY_MAP, ed è tempo che non voglio sprecare. Qualsiasi miglioramento della leggibilità è un vantaggio e le conseguenze sulle prestazioni sono minime, quindi mi sembra l'opzione migliore.
Jules il

18

Forse è interessante controllare le raccolte di Google , ad esempio i video che hanno sulla loro pagina. Forniscono vari modi per inizializzare mappe e set e forniscono anche raccolte immutabili.

Aggiornamento: questa libreria ora è denominata Guava .


17

Mi piace la classe anonima, perché è facile affrontarla:

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

Se dichiariamo più di una costante, quel codice verrà scritto in blocco statico e sarà difficile da mantenere in futuro. Quindi è meglio usare una classe anonima.

public class Test {

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

E si suggerisce di usare una mappa non modificabile per costanti altrimenti non può essere trattata come costante.


10

Potrei fortemente suggerire lo stile di "inizializzazione doppia parentesi" rispetto allo stile di blocco statico.

Qualcuno potrebbe commentare che non gli piacciono le lezioni anonime, le spese generali, le prestazioni, ecc.

Ma ciò che più considero è la leggibilità e la manutenibilità del codice. In questo punto di vista, sostengo che una doppia parentesi è uno stile di codice migliore piuttosto che un metodo statico.

  1. Gli elementi sono nidificati e in linea.
  2. È più OO, non procedurale.
  3. l'impatto sulle prestazioni è davvero ridotto e potrebbe essere ignorato.
  4. Migliore supporto della struttura IDE (piuttosto che molti blocchi anonimi {} statici)
  5. Hai salvato alcune righe di commento per portare loro una relazione.
  6. Impedire l'eventuale perdita dell'elemento / derivazione dell'istanza di un oggetto non inizializzato dall'eccezione e dall'ottimizzatore del bytecode.
  7. Nessuna preoccupazione per l'ordine di esecuzione del blocco statico.

Inoltre, sei a conoscenza del GC della classe anonima, puoi sempre convertirlo in una normale HashMap usando new HashMap(Map map).

Puoi farlo fino a quando non hai affrontato un altro problema. In tal caso, è necessario utilizzare un altro stile di codifica completo (ad es. Nessuna statica, classe di fabbrica).


8

Come al solito apache-commons ha il metodo corretto MapUtils.putAll (Map, Object []) :

Ad esempio, per creare una mappa dei colori:

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

Includo Apache Commons in tutte le build, quindi, nella sfortunata assenza di un metodo Arrays.asMap( ... )in Java, penso che questa sia la soluzione migliore. Reinventare la ruota è di solito sciocco. Il rovescio della medaglia è che con i generici avrà bisogno di una conversione incontrollata.
mike rodent,

La versione di @mikerodent 4.1 è generica: statica pubblica <K, V> Mappa <K, V> putAll (mappa finale <K, V> mappa, oggetto finale [] array)
agad

Tx ... sì, sto usando 4.1 ma devo ancora SuppressWarnings( unchecked )in Eclipse con una linea simileMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
mike rodent,

@mikerodent non è a causa di Object [] [] ? Vedi unswear aggiornato - Non ho alcun avviso in Eclipse.
agad,

Che strano ... anche quando vado String[][]ricevo l '"avvertimento"! E ovviamente funziona solo se la tua Ke la tua Vstessa classe. Presumo che tu abbia (comprensibilmente) impostato "conversione non selezionata" su "Ignora" nella tua configurazione di Eclipse?
Mike Rodent,

7

Ecco il mio preferito quando non voglio (o non posso) usare Guava ImmutableMap.of()o se ho bisogno di un mutevole 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]);
        }
    }};
}

È molto compatto e ignora i valori randagi (ovvero una chiave finale senza un valore).

Uso:

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

7

Se si desidera mappa immodificabile, infine java 9 aggiunto un metodo fresco di fabbrica ofper Mapl'interfaccia. Un metodo simile viene aggiunto anche a Set, List.

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


6

Preferisco usare un inizializzatore statico per evitare di generare classi anonime (che non avrebbero ulteriori scopi), quindi elencherò i suggerimenti per l'inizializzazione con un inizializzatore statico. Tutte le soluzioni / i suggerimenti elencati sono sicuri per i tipi.

Nota: la domanda non dice nulla su come rendere la mappa immodificabile, quindi lo tralascio, ma so che può essere facilmente risolto Collections.unmodifiableMap(map).

Primo consiglio

Il primo consiglio è che puoi fare un riferimento locale alla mappa e dargli un nome BREVE:

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

Secondo consiglio

Il secondo suggerimento è che è possibile creare un metodo di supporto per aggiungere voci; puoi anche rendere pubblico questo metodo di supporto se vuoi:

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

Il metodo helper qui non è riutilizzabile perché può solo aggiungere elementi a myMap2. Per renderlo riutilizzabile, potremmo rendere la mappa stessa un parametro del metodo helper, ma il codice di inizializzazione non sarebbe più breve.

Terzo consiglio

Il terzo suggerimento è che puoi creare una classe helper riutilizzabile simile a un costruttore con la funzionalità di popolamento. Questa è davvero una semplice classe di helper a 10 righe che è sicura per i tipi:

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

La classe anonima che stai creando funziona bene. Tuttavia dovresti essere consapevole che questa è una classe interna e come tale conterrà un riferimento all'istanza di classe circostante. Quindi scoprirai che non puoi farci certe cose (usando XStream per uno). Riceverai alcuni errori molto strani.

Detto questo, fintanto che ne sei consapevole, questo approccio va bene. Lo uso il più delle volte per inizializzare tutti i tipi di collezioni in modo conciso.

EDIT: sottolineato correttamente nei commenti che questa è una classe statica. Ovviamente non l'ho letto abbastanza da vicino. Tuttavia i miei commenti si applicano ancora alle classi interne anonime.


3
In questo caso particolare è statico, quindi nessuna istanza esterna.
Tom Hawtin - tackline

Probabilmente XStream non dovrebbe cercare di serializzare roba del genere (è statico. Perché dovresti serializzare una variabile statica?)
jasonmp85,

5

Se vuoi qualcosa di conciso e relativamente sicuro, puoi semplicemente spostare il controllo del tipo di compilazione in fase di esecuzione:

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

Questa implementazione dovrebbe rilevare eventuali errori:

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

Con Java 8 sono arrivato ad usare il seguente modello:

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

Non è la più concisa e un po 'rotonda, ma

  • non richiede nulla al di fuori di java.util
  • è dattiloscritto e accetta facilmente diversi tipi di chiave e valore.

se necessario, è possibile utilizzare la toMapfirma incluso un fornitore di mappe per specificare il tipo di mappa.
zrvan,


4

È possibile utilizzare StickyMape MapEntryda Cactoos :

private static final Map<String, String> MAP = new StickyMap<>(
  new MapEntry<>("name", "Jeffrey"),
  new MapEntry<>("age", "35")
);

4

Il tuo secondo approccio (inizializzazione di Double Brace) è pensato per essere un anti pattern , quindi sceglierei il primo approccio.

Un altro modo semplice per inizializzare una mappa statica è utilizzare questa funzione di utilità:

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

Nota: in Java 9puoi usare Map.of


3

Non mi piace la sintassi dell'inizializzatore statico e non sono convinto di sottoclassi anonime. In generale, sono d'accordo con tutti i contro dell'uso di inizializzatori statici e tutti i contro dell'uso di sottoclassi anonime che sono state menzionate nelle risposte precedenti. D'altra parte, i professionisti presentati in questi post non sono sufficienti per me. Preferisco usare il metodo di inizializzazione statica:

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

Non ho visto l'approccio che uso (e sono cresciuto fino a piacere) pubblicato in nessuna risposta, quindi eccolo qui:

Non mi piace usare gli inizializzatori statici perché sono goffi e non mi piacciono le classi anonime perché sta creando una nuova classe per ogni istanza.

preferisco invece l'inizializzazione simile a questa:

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

sfortunatamente, questi metodi non fanno parte della libreria Java standard, quindi sarà necessario creare (o utilizzare) una libreria di utilità che definisce i seguenti metodi:

 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)

(puoi usare "import static" per evitare di dover aggiungere il prefisso al nome del metodo)

Ho trovato utile fornire metodi statici simili per le altre raccolte (elenco, set, sortSet, sortMap, ecc.)

Non è bello come l'inizializzazione di un oggetto json, ma è un passo in quella direzione, per quanto riguarda la leggibilità.


3

Poiché Java non supporta i letterali delle mappe, le istanze delle mappe devono sempre essere istanziate e popolate in modo esplicito.

Fortunatamente, è possibile approssimare il comportamento dei letterali delle mappe in Java usando i metodi di fabbrica .

Per esempio:

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

Produzione:

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

È molto più conveniente della creazione e del popolamento della mappa di un elemento alla volta.


2

JEP 269 fornisce alcuni metodi predefiniti di fabbrica per l'API Collections. Questi metodi di fabbrica non sono nella versione Java corrente, che è 8, ma sono previsti per la versione Java 9.

Perché Mapci sono due metodi di fabbrica: ofe ofEntries. Usando of, puoi passare coppie chiave / valore alternate. Ad esempio, per creare un Maplike {age: 27, major: cs}:

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

Attualmente ci sono dieci versioni sovraccaricate per of, quindi puoi creare una mappa contenente dieci coppie chiave / valore. Se non ti piace questa limitazione o alternando chiave / valori, puoi utilizzare ofEntries:

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

Entrambi ofe ofEntriesrestituiranno un valore immutabile Map, quindi non puoi cambiare i loro elementi dopo la costruzione. Puoi provare queste funzionalità usando JDK 9 Early Access .


2

Beh ... mi piacciono gli enumer;)

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

Ho letto le risposte e ho deciso di scrivere il mio costruttore di mappe. Sentiti libero di copiare e incollare e divertiti.

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: Ultimamente, continuo a trovare il metodo statico pubblico of abbastanza spesso e mi piace un po '. L'ho aggiunto al codice e ho reso il costruttore privato, passando così al modello di metodo statico di fabbrica.

EDIT2: Ancora più recentemente, non mi piace più il metodo statico chiamato of, poiché sembra piuttosto male quando si usano le importazioni statiche. L'ho rinominato mapOfinvece, rendendolo più adatto per le importazioni statiche.

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.