Jak bezpośrednio zainicjować HashMap (dosłownie)?

1091

Czy istnieje jakiś sposób na zainicjowanie Java HashMap w ten sposób ?:

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

Jaka byłaby poprawna składnia? Nie znalazłem nic na ten temat. czy to możliwe? Szukam najkrótszego / najszybszego sposobu umieszczenia niektórych wartości „końcowych / statycznych” na mapie, które nigdy się nie zmieniają i są znane z góry podczas tworzenia mapy.

jens
źródło
Ściśle związane: stackoverflow.com/questions/507602/... (Oba pytania dotyczą inicjalizacji stałej mapy ze statycznymi, końcowymi wartościami.)
Jonik
stackoverflow.com/questions/36951414/…
Ramkumar Pillai 30.04.2016
W Javie 9: techiedelight.com/initialize-map-java9
Kamil Tomasz Jarmusik
Jeśli używasz apache.commons.collections, możesz użyć commons.apache.org/proper/commons-collections/javadocs/…
ax.

Odpowiedzi:

1341

Wszystkie wersje

Jeśli potrzebujesz tylko jednego wpisu: istnieje Collections.singletonMap("key", "value").

W przypadku wersji Java 9 lub nowszej:

Tak, jest to teraz możliwe. W Javie 9 dodano kilka fabrycznych metod, które upraszczają tworzenie map:

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

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

W powyższym przykładzie oba testi test2będzie taki sam, tylko z różnych sposobów wyrażania mapę. Map.ofMetoda jest zdefiniowana przez okres do dziesięciu elementów mapy, podczas gdy Map.ofEntriesmetoda nie będzie miał takiego limitu.

Zauważ, że w tym przypadku wynikowa mapa będzie mapą niezmienną. Jeśli chcesz, aby mapa była zmienna, możesz skopiować ją ponownie, np. UżywającmutableMap = new HashMap<>(Map.of("a", "b"));

(Zobacz także JEP 269 i Javadoc )

Dla wersji Java 8:

Nie, będziesz musiał ręcznie dodać wszystkie elementy. Możesz użyć inicjalizatora w anonimowej podklasie, aby skrócić składnię:

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

Jednak w niektórych przypadkach anonimowa podklasa może powodować niepożądane zachowanie. Obejmuje to na przykład:

  • Generuje dodatkową klasę, która zwiększa zużycie pamięci, zużycie miejsca na dysku i czas uruchamiania
  • W przypadku metody niestatycznej: zawiera odniesienie do obiektu, do którego wywołano metodę tworzenia. Oznacza to, że obiekt klasy zewnętrznej nie może być odśmiecany, gdy do utworzonego obiektu mapy nadal się odwołuje, co blokuje dodatkową pamięć

Użycie funkcji do inicjalizacji pozwoli ci również wygenerować mapę w inicjalizatorze, ale pozwoli uniknąć nieprzyjemnych efektów ubocznych:

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

private static Map<String, String> createMap() {
    Map<String,String> myMap = new HashMap<String,String>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    return myMap;
}
Jankes
źródło
3
To nie zadziała, jeśli chcesz zainicjować elementy funkcji ...
Michael
9
@Michael: Cóż, tak, jeśli chcesz użyć funkcji, nie możesz użyć niefunkcji. Ale dlaczego chcesz?
yankee
6
a w przypadkach, gdy potrzebujesz mapy z jednym wpisem, jest Collections.singletonMap():)
skwisgaar
3
Teraz, gdy została wydana stabilna Java 9, wolę ten link do Javadoc . I +1, ponieważ jedna mniejsza zależność!
Franklin Yu
3
Gdzie jest entryudokumentowane Java 9 ?
nobar
1029

To jest jeden sposób.

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

Należy jednak zachować ostrożność i upewnić się, że rozumiesz powyższy kod (tworzy on nową klasę, która dziedziczy po HashMap). Dlatego powinieneś przeczytać więcej tutaj: http://www.c2.com/cgi/wiki?DoubleBraceInitialization , lub po prostu użyj Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
gregory561
źródło
72
Działa, ale jest brzydki i ma niewidoczne skutki uboczne, które użytkownik powinien zrozumieć przed zrobieniem - na przykład generowanie całej anonimowej klasy na miejscu.
jprete
96
tak, tak napisałem o byciu ostrożnym i podałem link do opisu.
gregory561,
6
Świetny link. Odwołanie w tym linku do GreencoddsTenthRuleOfProgramming jest warte przeczytania.
michaelok
19
czy możesz dodać „jako ImmutableMap.builder.put („ k1 ”,„ v1 ”). put („ k2 ”,„ v2 ”). build ()”, ponieważ metoda „of” jest ograniczona maksymalnie do 5 par?
kommradHomer
341

Jeśli pozwolisz bibliotekami 3rd party, można użyć Guava „s ImmutableMap osiągnąć podobny dosłowny zwięzłości:

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

Działa to dla maksymalnie 5 par klucz / wartość , w przeciwnym razie możesz użyć jego konstruktora :

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


  • zauważ, że implementacja Guava ImmutableMap różni się od implementacji Java HashMap (przede wszystkim jest niezmienna i nie pozwala na zerowe klucze / wartości)
  • Aby uzyskać więcej informacji, zobacz artykuł Guava w podręczniku użytkownika na temat niezmiennych typów kolekcji
Jens Hoffmann
źródło
26
Ponadto guava ma ImmutableMap.builder.put („k1”, „v1”). Put („k2”, „v2”). Build ();
Xetius
17
ImmutableMap to nie to samo co HashMap, ponieważ zawiedzie przy zerowych wartościach, podczas gdy map HashMap nie.
Gewthen
2
Aby pomóc innym, którzy mogą napotkać ten problem. Musisz wpisać konstruktor, aby przekształcić go w Map <String, String>, jak poniżej: Map <String, String> test = ImmutableMap. <String, String> builder (). Put („k1”, „v1”). put („k2”, „v2”). build ();
Thiago,
to jest niesamowite Jens!
gaurav
105

Nie ma na to bezpośredniego sposobu - Java nie ma literałów mapy (jeszcze - myślę, że zostały one zaproponowane dla Java 8).

Niektórzy lubią to:

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

To tworzy anonimową podklasę HashMap, której inicjator instancji umieszcza te wartości. (Nawiasem mówiąc, mapa nie może zawierać dwa razy tej samej wartości, twoje drugie wstawienie zastąpi pierwszą. Użyję różnych wartości dla następnych przykładów.)

Normalny sposób byłby następujący (dla zmiennej lokalnej):

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

Jeśli twoja testmapa jest zmienną instancji, umieść inicjalizację w konstruktorze lub inicjatorze instancji:

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

Jeśli twoja testmapa jest zmienną klasową, umieść inicjalizację w inicjalizatorze statycznym:

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

Jeśli chcesz, aby twoja mapa nigdy się nie zmieniała, po inicjalizacji powinieneś ją owinąć Collections.unmodifiableMap(...). Możesz to zrobić również w statycznym inicjalizatorze:

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

(Nie jestem pewien, czy możesz teraz dokonać testostatecznego ... wypróbuj go i zgłoś się tutaj).

Paŭlo Ebermann
źródło
61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};
Kudłaty żaba
źródło
Prosto i do rzeczy. Myślę, że najlepszą odpowiedzią będzie ten z rozszerzoną sekcją komentarzy.
ooolala
15
Należy jednak pamiętać o implikacjach pamięci. blog.jooq.org/2014/12/08/…
Amalgovinus
1
@Amalgovinus Zasadniczo, tworząc nową podklasę, programujesz na stałe argumenty typu HashMapw tej podklasie. Może to działać tylko wtedy, gdy faktycznie je udostępnisz. (W nowej (pustej) HashMap argumenty typu nie są istotne.)
Paŭlo Ebermann
1
Podoba mi się jego czystość, ale tworzy niepotrzebną anonimową klasę i ma problemy opisane tutaj: c2.com/cgi/wiki?DoubleBraceInitialization
udachny
1
@hello_its_me: Ponieważ jest taki sam jak stackoverflow.com/a/6802512/1386911 odpowiedź, tylko inne formatowanie. I w tym przypadku to rozszerzone formatowanie nie ma dodatkowej wartości w stosunku do formatu kompaktowego dla czytelności.
Daniel Hári,
44

Alternatywą jest użycie klas Java 7 i varargs: utwórz klasę za HashMapBuilderpomocą tej metody:

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

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

    String key = null;
    Integer step = -1;

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

    return result;
}

Użyj następującej metody:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
Aerthel
źródło
Napisałem odpowiedź zainspirowaną twoją: stackoverflow.com/questions/507602/...
GeroldBroser przywraca Monikę
1
Inne rozwiązanie z Apache Utils, które nigdy nie jest wspomniane, ale jest czytelne, przy użyciu wcześniejszych wersji Java: MapUtils.putAll (nowy HashMap <Łańcuch, Łańcuch> (), nowy Obiekt [] {„Mój klucz”, „moja wartość”, ...
Rolintocour,
3

tl; dr

Użyj Map.of…metod w Javie 9 i nowszych.

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

Map.of

Java 9 dodała szereg Map.ofstatycznych metod, aby zrobić dokładnie to, co chcesz: utworzyć instancję niezmiennego Mapza pomocą składni literalnej .

Mapa (zbiór wpisów) jest niezmienna, więc nie można dodawać ani usuwać wpisów po utworzeniu wystąpienia. Ponadto klucz i wartość każdego wpisu są niezmienne, nie można ich zmienić. Zobacz Javadoc, aby poznać inne reguły, takie jak niedozwolone wartości NULL, niedozwolone duplikaty kluczy, a kolejność odwzorowań jest dowolna.

Spójrzmy na te metody, wykorzystując przykładowe dane do mapy dnia tygodnia dla osoby, która, jak się spodziewamy, będzie pracować tego dnia.

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

Map.of()

Map.oftworzy puste Map. Niemodyfikowalna, więc nie można dodawać wpisów. Oto przykład takiej mapy, pustej bez wpisów.

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

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)jest kilka metod, które przyjmują od 1 do 10 par klucz-wartość. Oto przykład dwóch wpisów.

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

weekendWorker.toString (): {NIEDZIELA = Osoba {imię = „Bob”}, SOBOTA = Osoba {imię = „Alice”}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )pobiera dowolną liczbę obiektów implementujących Map.Entryinterfejs. Java łączy dwie klasy implementujące ten interfejs, jedną zmienną, drugą niezmienną: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Ale nie musimy określać konkretnej klasy. Musimy tylko wywołać Map.entry( k , v )metodę, przekazać nasz klucz i naszą wartość, i otrzymamy obiekt Map.Entryinterfejsu implementacyjnego klasy .

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

weekdayWorker.toString (): {ŚRODA = Osoba {imię = 'Bob'}, WTOREK = Osoba {imię = 'Bob'}, CZWARTEK = Osoba {imię = 'Carol'}, PIĄTEK = Osoba {name = 'Carol'} , PONIEDZIAŁEK = Osoba {name = 'Alice'}}

Map.copyOf

Java 10 dodała tę metodę Map.copyOf. Minąć istniejącą mapę, odzyskać niezmienną kopię tej mapy.

Notatki

Zauważ, że kolejność iterator map produkowane przez Map.ofsię nie jest gwarantowana. Wpisy mają dowolny porządek. Nie pisz kodu w oparciu o widziane zamówienie, ponieważ dokumentacja ostrzega, że ​​zamówienie może ulec zmianie.

Należy pamiętać, że wszystkie te Map.of…metody zwracają Mapz nieokreślonej klasy . Podstawowa konkretna klasa może nawet różnić się w zależności od wersji Java. Ta anonimowość pozwala Java wybierać spośród różnych implementacji, niezależnie od tego, które optymalnie pasują do konkretnych danych. Na przykład, jeśli klucze pochodzą z wyliczenia , Java może użyć EnumMappod okładkami.

Basil Bourque
źródło
1

Możesz łatwo stworzyć własną Map.ofmetodę (która jest dostępna tylko w Javie 9 i wyższych) na 2 proste sposoby

Zrób to z określoną ilością parametrów

Przykład

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

Zrób to za pomocą listy

Możesz to zrobić za pomocą listy, zamiast tworzyć wiele metod dla określonego zestawu parametrów.

Przykład

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

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

Uwaga Nie zaleca się używania tego do wszystkiego, ponieważ tworzy to anonimową klasę za każdym razem, gdy z niej korzystasz.

NotNV6
źródło
1

JAVA 8

W zwykłym java 8 masz również możliwość skorzystania Streams/Collectorsz tego zadania.

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

Ma to tę zaletę, że nie tworzy klasy Anonimowej.

Pamiętaj, że importowane są:

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

Oczywiście, jak zauważono w innych odpowiedziach, w Javie 9 i nowszej masz prostsze sposoby robienia tego samego.

Johnny Willer
źródło
0

Niestety używanie varargs, jeśli typ kluczy i wartości nie są takie same, nie jest zbyt rozsądne, ponieważ trzeba by Object...całkowicie użyć i utracić bezpieczeństwo typu. Jeśli zawsze chcesz stworzyć np. A Map<String, String>, oczywiście toMap(String... args)byłoby to możliwe, ale niezbyt ładne, ponieważ łatwo byłoby mieszać klucze i wartości, a nieparzysta liczba argumentów byłaby niepoprawna.

Możesz utworzyć podklasę HashMap, która ma metodę łańcuchową taką jak

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

i używaj go jak new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Innym podejściem jest użycie wspólnego wzorca konstruktora:

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

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

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

i używaj go jak new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Jednak rozwiązanie, z którego korzystałem od czasu do czasu, wykorzystuje varargs i Pairklasę:

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

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

    return map;
  }
}

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

Gadatliwość Pair.create()trochę mi przeszkadza, ale to działa całkiem dobrze. Jeśli nie masz nic przeciwko importowi statycznemu, możesz oczywiście utworzyć pomocnika:

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

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

(Zamiast tego Pairmożna sobie wyobrazić użycie Map.Entry, ale ponieważ jest to interfejs, wymaga klasy implementacyjnej i / lub metody fabryki pomocników. Nie jest również niezmienny i zawiera inną logikę nieprzydatną do tego zadania).

JHH
źródło
0

Możesz używać strumieni w Javie 8 (jest to przykład Seta):

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

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

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

Robocide
źródło
0

Jeśli potrzebujesz umieścić tylko jedną parę klucz-wartość, możesz użyć Collections.singletonMap (klucz, wartość);

Balakrishna Kudikala
źródło
1
formatowanie kodu czyni post znacznie bardziej czytelnym
Renato,