Jak mogę zainicjować statyczną mapę?


1131

Jak zainicjowałbyś statyczny kod Mapw Javie?

Metoda pierwsza: inicjalizator statyczny
Metoda druga: inicjator instancji (anonimowa podklasa) lub inna metoda?

Jakie są zalety i wady każdego z nich?

Oto przykład ilustrujący dwie metody:

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
Aby zainicjować mapę w Javie 8: stackoverflow.com/a/37384773/1216775
akhil_mittal

2
Nigdy nie używaj inicjalizacji podwójnego nawiasu klamrowego - jest to hack i łatwy sposób na wyciek pamięci i spowodowanie innych problemów.
dimo414,

Java 9? Jeśli wpisy liczą <= 10 użyj Map.ofinnego Map.ofEntries, sprawdź stackoverflow.com/a/37384773/1216775
akhil_mittal

Odpowiedzi:


1106

Inicjator wystąpienia jest w tym przypadku tylko cukrem syntaktycznym, prawda? Nie rozumiem, dlaczego potrzebujesz dodatkowej anonimowej klasy tylko do inicjalizacji. I to nie zadziała, jeśli tworzona klasa jest ostateczna.

Możesz stworzyć niezmienną mapę za pomocą statycznego inicjalizatora:

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
Z tego idiomu korzystam od lat i nigdy nie miałem na to oko. Robię to samo dla niezmiennych stałych zestawów i list.
jasonmp85

3
Jak miałbym obsługiwać HashMap <Ciąg, Ciąg> za pomocą klucza Ciąg. Obiekt Map nie pozwala mi mieć klucza String, więc nie mogę używać unmodifiableMap (). Wydaje mi się, że rzutowanie na HashMap również pokona ten cel. Jakieś pomysły?
Łukasz

30
@Luke Poważnie wątpię, że Android ma takie ograniczenie. To nie ma w ogóle sensu. Szybkie wyszukiwanie znalazło to pytanie tutaj (i wiele innych), które wydaje się sugerować, że możesz użyć klucza String do obiektu mapy w systemie Android.
mluisbrown

11
Więc nikt nie zawraca głowy badaniem, mogę potwierdzić, że nie ma problemu z użyciem klucza String dla obiektu Map na Androidzie.
Jordan

11
Jordan: jest to teraz stary temat, ale podejrzewam, że @Luke próbował użyć łańcucha jako klucza na mapie, która miała inny typ klucza, np. Map <Integer, String>.
Nędzna zmienna

445

Podoba mi się sposób Guawy inicjowania statycznej, niezmiennej mapy:

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

Jak widać, jest bardzo zwięzły (ze względu na wygodne metody fabryczne w ImmutableMap).

Jeśli chcesz, aby mapa zawierała więcej niż 5 wpisów, nie możesz już jej używać ImmutableMap.of(). Zamiast tego spróbuj wykonać ImmutableMap.builder()następujące czynności:

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

Aby dowiedzieć się więcej o zaletach niezmiennych narzędzi do gromadzenia Guava, zobacz Immutable Collections wyjaśnione w Guava User Guide .

(Podzbiór) Guawa nazywała się kolekcjami Google . Jeśli nie korzystasz jeszcze z tej biblioteki w swoim projekcie Java, zdecydowanie zalecamy jej wypróbowanie! Guava szybko stała się jedną z najpopularniejszych i najbardziej przydatnych darmowych bibliotek stron trzecich dla Javy, jak zgadzają się inni użytkownicy SO . (Jeśli dopiero zaczynasz przygodę, za tym linkiem kryją się doskonałe zasoby edukacyjne).


Aktualizacja (2015) : Jeśli chodzi o Javę 8 , nadal używałbym podejścia Guava, ponieważ jest znacznie czystsze niż cokolwiek innego. Jeśli nie chcesz zależności Guava, rozważ prostą, starą metodę init . Hack z dwuwymiarową tablicą i Stream API jest dość brzydki, jeśli mnie zapytasz, i staje się brzydszy, jeśli musisz utworzyć mapę, której klucze i wartości nie są tego samego typu (jak Map<Integer, String>w pytaniu).

Jeśli chodzi o przyszłość Guavy w ogóle, w odniesieniu do Java 8, Louis Wasserman powiedział to w 2014 roku, a [ aktualizacja ] w 2016 roku ogłoszono, że Guava 21 będzie wymagał i poprawnie obsługiwał Javę 8 .


Aktualizacja (2016) : jak zauważa Tagir Valeev , Java 9 w końcu sprawi, że będzie to łatwe , używając wyłącznie JDK, dodając metody fabryczne wygody dla kolekcji:

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

21
Wygląda na to, że nasi inni administratorzy SO usunęli czcigodne pytanie „Najbardziej przydatne bezpłatne biblioteki Java stron trzecich”, do którego odsyłam. :( Cholera.
Jonik

2
Zgadzam się, to najlepszy sposób na zainicjowanie stałej mapy. Nie tylko bardziej czytelny, ale także ponieważ Collection.unmodifiableMap zwraca widok tylko do odczytu mapy podstawowej (którą nadal można modyfikować).
crunchdog

11
Widzę teraz usunięte pytania (z 10k + powtórzeniami), więc oto kopia „Najbardziej przydatnych darmowych bibliotek Java innych firm” . To tylko pierwsza strona, ale przynajmniej możesz znaleźć wyżej wymienione zasoby Guawy .
Jonik

2
Naprawdę wolę to podejście, chociaż dobrze jest wiedzieć, jak to zrobić bez dodatkowych zależności.
Klucz

2
JEP 186 nadal nie jest zamknięty, więc może wprowadzić nowe funkcje związane z literałami dotyczącymi kolekcji
cybersoft

182

Użyłbym:

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. unika anonimowej klasy, którą osobiście uważam za zły styl i której unikam
  2. sprawia, że ​​tworzenie mapy jest bardziej wyraźne
  3. czyni mapę niemodyfikowalną
  4. ponieważ MY_MAP jest stały, nazwałbym to jak stałym

3
Spośród czystych opcji JDK (bez bibliotek) najbardziej to lubię, ponieważ definicja mapy jest wyraźnie powiązana z jej inicjalizacją. Zgodziliśmy się także na stałe nazewnictwo.
Jonik

Nigdy nie przyszło mi do głowy, że możesz to zrobić.
romulusnr

181

Java 5 zapewnia bardziej zwartą składnię:

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

46
Ta technika nazywa się inicjowaniem podwójnego nawiasu klamrowego: stackoverflow.com/questions/1372113/... To nie jest specjalna składnia Java 5, to tylko sztuczka z anonimową klasą z inicjatorem instancji.
Jesper

13
Szybkie pytanie dotyczące inicjalizacji podwójnego nawiasu klamrowego: w tym czasie Eclipse wyświetla Ostrzeżenie o brakującym numerze seryjnym. Z jednej strony nie rozumiem, dlaczego w tym konkretnym przypadku potrzebny byłby numer seryjny, ale z drugiej strony zwykle nie lubię tłumić ostrzeżeń. Co o tym myślisz?
nbarraille,

8
@nbarraille To dlatego, że HashMap implements Serializable. Ponieważ faktycznie tworzysz podklasę HashMap za pomocą tej „sztuczki”, domyślnie tworzysz klasę możliwą do serializacji. I do tego należy podać serialUID.
nikt nie

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 - Sugerowane użycie to kontekst statyczny. Wydajność może być gorsza, ale nie zauważalna, gdy ma się do czynienia z przypuszczalnie małą liczbą statycznie zdefiniowanych map. HashMap.equalsjest zdefiniowany w dowolnej podklasie mapy AbstractMapi działa na niej , więc nie stanowi to problemu. Operator diamentów jest denerwujący, ale jak już wspomniano, został już rozwiązany.
Jules

95

Zaletą drugiej metody jest to, że można ją owinąć, Collections.unmodifiableMap()aby zagwarantować, że nic nie będzie później aktualizować kolekcji:

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
Czy nie możesz tego łatwo zrobić w pierwszej metodzie, przenosząc nowego operatora do bloku static {} i owijając go?
Patrick,

2
W każdym razie przeniósłbym wywołanie konstruktora do statycznej inicjalizacji. Wszystko inne wygląda dziwnie.
Tom Hawtin - tackline

2
jakiś pomysł, jaki wpływ może mieć wydajność korzystania z anonimowej klasy, a nie z konkretnej klasy?
Kip

62

Oto jednowierszowy inicjalizator mapy statycznej 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]));

Edycja: aby zainicjować Map<Integer, String>jak w pytaniu, potrzebujesz czegoś takiego:

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

Edycja (2): i_am_zero oferuje lepszą, kompatybilną z typem wersję, która wykorzystuje strumień new SimpleEntry<>(k, v)wywołań. Sprawdź tę odpowiedź: https://stackoverflow.com/a/37384773/3950982


7
Pozwoliłem sobie dodać wersję, która jest równoważna z pytaniem i innymi odpowiedziami: zainicjuj mapę, której klucze i wartości są innego rodzaju (więc String[][]nie zrobię tego, Object[][]jest potrzebne). IMHO, takie podejście jest brzydkie (tym bardziej z obsadami) i trudne do zapamiętania; nie użyłbym tego sam.
Jonik,

57

Map.of w Javie 9+

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

Szczegóły w JEP 269 . JDK 9 osiągnął ogólną dostępność we wrześniu 2017 r.


7
Lub jeśli chcesz więcej niż 10 par klucz-wartość, możesz użyćMap.ofEntries
ZhekaKozlov

8
To wszystko jest czyste, dopóki nie zdasz sobie sprawy z tego
połowie

Ugh, to takie smutne - wygląda na to, że obsługuje tylko 10 wpisów, po których musisz użyć Entries. Kulawy.
Somaiah Kumbera

2
Czystość wdrożenia w JDK nie powinna mieć znaczenia, dopóki działa i spełnia warunki umowy. Jak każda czarna skrzynka, szczegóły implementacji można zawsze naprawić w przyszłości, jeśli naprawdę będą potrzebne ...
vikingsteve 31.01.19

@mid To jedyny bezpieczny sposób na zrobienie tego w Javie.
Luke Hutchison

44

Java 9

Możemy użyć Map.ofEntries, dzwoniąc, Map.entry( k , v )aby utworzyć każdy wpis.

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

Możemy również użyć, Map.ofjak sugeruje Tagir w swojej odpowiedzi tutaj, ale nie możemy użyć więcej niż 10 wpisów Map.of.

Java 8 (Neat Solution)

Możemy stworzyć Strumień wpisów na mapie. Mamy już dwie implementacje, Entryw java.util.AbstractMapktórych SimpleEntry i SimpleImmutableEntry . W tym przykładzie możemy użyć byłego jako:

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
Ta new SimpleEntry<>()droga jest znacznie mniej czytelna niż statyczna put(): /
Danon

32

W przypadku kolekcji Eclipse działają wszystkie następujące elementy:

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

Możesz także statycznie inicjować prymitywne mapy za pomocą Eclipse Collections.

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

Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse


1
Naprawdę chciałbym, aby Eclipse Collections była domyślną biblioteką kolekcji dla Javy. Lubię to znacznie bardziej niż Guava + JCL.
Kenny Cason,

29

W tej sytuacji nigdy nie stworzyłbym anonimowej podklasy. Inicjatory statyczne działają równie dobrze, jeśli na przykład chcesz uniemożliwić modyfikację mapy:

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
W jakiej sytuacji użyłbyś anonimowej podklasy, aby zainicjować skrót?
dogbane

6
Nigdy nie inicjuj kolekcji.
eljenso

Czy możesz wyjaśnić, dlaczego użycie statycznego inicjatora jest lepszym wyborem niż tworzenie anonimowej podklasy?
leba-lev

3
@rookie Istnieje kilka powodów podanych w innych odpowiedziach na korzyść statycznego init. Celem jest tutaj inicjalizacja, więc po co wprowadzać podklasę, może poza kilkoma naciśnięciami klawiszy? (Jeśli chcesz zaoszczędzić na naciśnięciach klawiszy, Java zdecydowanie nie jest dobrym wyborem jako język programowania.) Jedną z praktycznych zasad, których używam podczas programowania w Javie jest: podklasa tak mała, jak to możliwe (i nigdy, kiedy można jej racjonalnie uniknąć).
eljenso

@eljenso - powodem, dla którego ogólnie preferuję składnię podklasy, jest umieszczenie inicjalizacji w miejscu, w którym należy . Drugim najlepszym wyborem jest wywołanie metody statycznej, która zwraca zainicjowaną mapę. Ale obawiam się, że przyjrzę się Twojemu kodowi i będę musiał poświęcić kilka sekund na zastanowienie się, skąd pochodzi MY_MAP, i to jest czas, którego nie chcę tracić. Każda poprawa czytelności jest zaletą, a konsekwencje wydajności są minimalne, więc wydaje mi się to najlepszą opcją.
Jules

18

Może warto sprawdzić Kolekcje Google , np. Filmy, które mają na swojej stronie. Zapewniają różne sposoby inicjowania map i zestawów, a także niezmienne kolekcje.

Aktualizacja: ta biblioteka nosi teraz nazwę Guava .


17

Lubię anonimowe zajęcia, ponieważ łatwo sobie z tym poradzić:

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

Jeśli zadeklarujemy więcej niż jedną stałą, kod ten zostanie zapisany w bloku statycznym i będzie to trudne do utrzymania w przyszłości. Lepiej więc użyć anonimowej klasy.

public class Test {

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

I sugeruje się stosowanie niemodyfikowalnej mapy dla stałych, w przeciwnym razie nie można jej traktować jako stałej.


10

Mógłbym zdecydowanie zasugerować styl „podwójnej inicjacji nawiasów klamrowych” zamiast stylu bloku statycznego.

Ktoś może komentować, że nie lubi anonimowych zajęć, kosztów ogólnych, wydajności itp.

Ale bardziej uważam to za czytelność kodu i łatwość konserwacji. Z tego punktu widzenia podwójny nawias klamrowy to lepszy styl kodu niż metoda statyczna.

  1. Elementy są zagnieżdżone i wbudowane.
  2. To bardziej OO, a nie proceduralne.
  3. wpływ na wydajność jest naprawdę niewielki i można go zignorować.
  4. Lepsza obsługa konturu IDE (zamiast wielu anonimowych bloków statycznych {})
  5. Zapisałeś kilka wierszy komentarza, aby przynieść im relację.
  6. Zapobiegaj możliwemu wyciekowi elementu / potencjalnemu wystąpieniu niezainicjowanego obiektu przed wyjątkiem i optymalizatorem kodu bajtowego.
  7. Nie martw się o kolejność wykonywania bloku statycznego.

Ponadto, jeśli znasz GC anonimowej klasy, zawsze możesz przekonwertować ją na normalną HashMap za pomocą new HashMap(Map map).

Możesz to zrobić, dopóki nie napotkasz innego problemu. Jeśli to zrobisz, powinieneś zastosować dla niego kompletny inny styl kodowania (np. Brak statycznej, fabrycznej klasy).


8

Jak zwykle apache-commons ma odpowiednią metodę MapUtils.putAll (Map, Object []) :

Na przykład, aby utworzyć mapę kolorów:

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

Do wszystkich kompilacji dołączam Apache Commons, więc przy niefortunnym braku metody Arrays.asMap( ... )w zwykłej Javie myślę, że to najlepsze rozwiązanie. Nowe odkrywanie koła jest zwykle głupie. Bardzo niewielkim minusem jest to, że w przypadku generyków będzie wymagał niezaznaczonej konwersji.
Mike gryzoni

Wersja @mikerodent 4.1 jest ogólna: publiczna statyczna <K, V> Mapa <K, V> putAll (ostateczna mapa <K, V> mapa, ostateczna tablica Object [])
agad

Tx ... tak, używam 4.1, ale wciąż muszę SuppressWarnings( unchecked )w Eclipse z linią podobną doMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
mike gryzoni

@mikerodent nie jest to spowodowane Object [] [] ? Zobacz zaktualizowane niepotrzebne - Nie mam żadnego ostrzeżenia w Eclipse.
agad

Jak dziwnie ... nawet kiedy idę String[][], dostaję „ostrzeżenie”! I oczywiście, że działa tylko jeśli Ki Vsą tej samej klasy. Rozumiem, że nie ustawiłeś (co zrozumiałe) „niezaznaczonej konwersji” na „Ignoruj” w konfiguracji Eclipse?
Mike Rodent

7

Oto moje ulubione, gdy nie chcę (lub nie mogę) używać Guawy ImmutableMap.of()lub jeśli potrzebuję mutable 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]);
        }
    }};
}

Jest bardzo kompaktowy i ignoruje zbłąkane wartości (tzn. Ostatni klucz bez wartości).

Stosowanie:

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

7

Jeśli chcesz niemodyfikowalne mapę, wreszcie java 9 dodaje metodę chłodny fabryki ofdo Mapinterfejsu. Podobną metodę dodano również do Set, List.

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


6

Wolę używać statycznego inicjalizatora, aby uniknąć generowania anonimowych klas (co nie miałoby żadnego dalszego celu), dlatego wymienię wskazówki dotyczące inicjowania przy użyciu statycznego inicjatora. Wszystkie wymienione rozwiązania / wskazówki są bezpieczne dla typu.

Uwaga: pytanie nie mówi nic o uczynieniu mapy niemodyfikowalną, więc pominę to, ale wiem, że można to łatwo zrobić Collections.unmodifiableMap(map).

Pierwsza wskazówka

Pierwsza wskazówka jest taka, że ​​możesz zrobić lokalne odniesienie do mapy i nadać jej KRÓTKĄ nazwę:

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

Druga wskazówka

Druga wskazówka jest taka, że ​​możesz utworzyć metodę pomocnika, aby dodać wpisy; możesz również upublicznić tę metodę pomocniczą, jeśli chcesz:

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

Metoda pomocnicza tutaj nie nadaje się do ponownego użycia, ponieważ może tylko dodawać elementy myMap2. Aby uczynić go ponownie użytecznym, moglibyśmy uczynić samą mapę parametrem metody pomocniczej, ale wtedy kod inicjujący nie byłby krótszy.

Trzecia wskazówka

Trzecia wskazówka polega na tym, że możesz stworzyć klasę pomocniczą podobną do konstruktora z wypełniającą funkcjonalnością. To naprawdę prosta, 10-liniowa klasa pomocnicza, która jest bezpieczna dla typu:

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

Anonimowa klasa, którą tworzysz, działa dobrze. Należy jednak pamiętać, że jest to klasa wewnętrzna i jako taka będzie zawierać odwołanie do instancji klasy otaczającej. Przekonasz się więc, że nie możesz z nim zrobić pewnych rzeczy (używając XStream dla jednego). Otrzymasz bardzo dziwne błędy.

To powiedziawszy, dopóki jesteś świadomy, to podejście jest w porządku. Używam go przez większość czasu do zwięzłego inicjowania wszelkiego rodzaju kolekcji.

EDYCJA: Poprawnie wskazano w komentarzach, że jest to klasa statyczna. Oczywiście nie przeczytałem tego wystarczająco dokładnie. Jednak moje komentarze mają nadal zastosowanie do anonimowych klas wewnętrznych.


3
W tym konkretnym przypadku jest statyczny, więc nie ma zewnętrznego wystąpienia.
Tom Hawtin - tackline

Prawdopodobnie XStream nie powinien próbować serializować takich rzeczy (jest statyczny. Dlaczego miałbyś
chcieć

5

Jeśli chcesz czegoś zwięzłego i względnie bezpiecznego, możesz po prostu zmienić sprawdzanie typu kompilacji na czas wykonywania:

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

Ta implementacja powinna wyłapać wszelkie błędy:

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

W Javie 8 zacząłem używać następującego wzorca:

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

To nie jest najbardziej zwięzłe i trochę ronda, ale

  • nie wymaga niczego poza java.util
  • jest bezpieczny dla typów i łatwo mieści różne typy dla klucza i wartości.

w razie potrzeby można użyć toMappodpisu, w tym dostawcy map, aby określić typ mapy.
zrvan



4

Twoje drugie podejście (inicjalizacja podwójnego nawiasu klamrowego) jest uważane za anty-wzór , więc wybrałbym pierwsze podejście.

Innym łatwym sposobem na zainicjowanie mapy statycznej jest użycie tej funkcji narzędzia:

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

Uwaga: Java 9możesz użyć Map.of


3

Nie lubię składni inicjalizatora statycznego i nie jestem przekonany do anonimowych podklas. Zasadniczo zgadzam się ze wszystkimi wadami używania statycznych inicjatorów i wszystkimi wadami korzystania z anonimowych podklas wymienionych w poprzednich odpowiedziach. Z drugiej strony - profesjonaliści zaprezentowani w tych postach nie są dla mnie wystarczające. Wolę używać metody inicjalizacji statycznej:

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

Nie widziałem podejścia, którego używam (i polubiłem), zamieszczonego w żadnej odpowiedzi, więc oto:

Nie lubię używać statycznych inicjatorów, ponieważ są niezgrabne i nie lubię anonimowych klas, ponieważ tworzy nowe klasy dla każdej instancji.

zamiast tego wolę inicjalizację, która wygląda następująco:

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

niestety metody te nie są częścią standardowej biblioteki Java, dlatego trzeba utworzyć (lub użyć) bibliotekę narzędzi, która definiuje następujące metody:

 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)

(możesz użyć opcji „importuj statyczny”, aby uniknąć konieczności prefiksu nazwy metody)

Uznałem, że użyteczne jest zapewnienie podobnych metod statycznych dla innych kolekcji (list, set, sortedSet, sortedMap itp.)

Nie jest tak przyjemny jak inicjalizacja obiektu json, ale jest to krok w tym kierunku, jeśli chodzi o czytelność.


3

Ponieważ Java nie obsługuje literałów map, instancje map muszą zawsze być jawnie tworzone i wypełniane.

Na szczęście możliwe jest przybliżenie zachowania literałów map w Javie przy użyciu metod fabrycznych .

Na przykład:

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

Wynik:

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

Jest to o wiele wygodniejsze niż tworzenie i wypełnianie mapy na raz elementem.


2

JEP 269 zapewnia pewne fabryczne metody dla interfejsu API kolekcji. Te metody fabryczne nie są w aktualnej wersji Java, czyli 8, ale są planowane w wersji Java 9.

Ponieważ Mapistnieją dwie metody fabryczne: ofi ofEntries. Za pomocą ofmożna przekazywać naprzemiennie pary klucz / wartość. Na przykład, aby utworzyć Mappodobny {age: 27, major: cs}:

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

Obecnie istnieje dziesięć przeciążonych wersji of, więc możesz utworzyć mapę zawierającą dziesięć par klucz / wartość. Jeśli nie podoba ci się to ograniczenie lub naprzemiennie klucz / wartości, możesz użyć ofEntries:

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

Oba ofi ofEntrieszwrócą niezmienne Map, więc nie możesz zmienić ich elementów po zakończeniu budowy. Możesz wypróbować te funkcje, korzystając z JDK 9 Early Access .


2

Cóż ... lubię śliwki;)

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

Przeczytałem odpowiedzi i postanowiłem napisać własny kreator map. Kopiuj-wklej i ciesz się.

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

EDYCJA: Ostatnio ofdość często znajduję publiczną metodę statyczną i trochę mi się podoba. Dodałem go do kodu i uczyniłem konstruktorem prywatnym, przełączając się w ten sposób na statyczny wzorzec metody fabryki.

EDYCJA 2: Jeszcze niedawno nie lubię wywoływanej metody statycznej of, ponieważ wygląda ona dość źle podczas korzystania z importu statycznego. mapOfZamiast tego zmieniłem nazwę na , dzięki czemu jest bardziej odpowiedni do importowania statycznego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.