Як безпосередньо ініціалізувати HashMap (буквально)?


1091

Чи існує такий спосіб ініціалізації Java HashMap таким чином:

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

Який би був правильний синтаксис? Я нічого з цього приводу не знайшов. Чи можливо це? Я шукаю найкоротший / найшвидший спосіб помістити деякі "кінцеві / статичні" значення на карту, які ніколи не змінюються і відомі заздалегідь при створенні Карти.



Тісно пов’язані: stackoverflow.com/questions/507602/… (Обидва питання стосуються ініціалізації постійної карти зі статичними, кінцевими значеннями.)
Jonik



Якщо ви використовуєте apache.commons.collections, ви можете використовувати commons.apache.org/proper/commons-collections/javadocs/…
ax.

Відповіді:


1341

Усі версії

У разі , якщо вам трапиться потрібно тільки один запис: Існує Collections.singletonMap("key", "value").

Для версії Java 9 або новішої:

Так, це можливо зараз. У Java 9 додано пару заводських методів, які спрощують створення карт:

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

У наведеному вище прикладі обидва testі test2будуть однакові, лише з різними способами вираження карти. Map.ofМетод визначено до десяти елементів в карті, в той час як Map.ofEntriesметод не матиме така межа.

Зауважте, що в цьому випадку отримана карта буде незмінною картою. Якщо ви хочете, щоб карта була змінною, ви можете скопіювати її знову, наприклад, використовуючиmutableMap = new HashMap<>(Map.of("a", "b"));

(Див. Також JEP 269 та Javadoc )

Для до версії Java 8:

Ні, всі елементи доведеться додавати вручну. Ви можете використовувати ініціалізатор в анонімному підкласі, щоб зробити синтаксис трохи коротшим:

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

Однак анонімний підклас може ввести небажану поведінку в деяких випадках. Сюди входить, наприклад:

  • Він створює додатковий клас, який збільшує споживання пам’яті, витрату дискового простору та час запуску
  • У разі нестатичного методу: він містить посилання на об'єкт, до якого був викликаний метод створення. Це означає, що об'єкт зовнішнього класу не може бути зібраний сміттям, поки на створений об’єкт карти все ще посилається, тим самим блокуючи додаткову пам'ять

Використання функції для ініціалізації також дасть змогу генерувати карту в ініціалізаторі, але уникає неприємних побічних ефектів:

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

3
Це не спрацює, якщо ви хочете ініціювати елементи у функції ...
Майкл

9
@Michael: Ну так, якщо ви хочете використовувати функцію, ніж ви не можете використовувати нефункціональну функцію. Але чому ти цього хочеш?
Янкі

6
а для тих випадків, коли вам потрібна Карта з одним записом, є Collections.singletonMap():)
skwisgaar

3
Тепер, коли стабільна Java 9 була випущена, я віддаю перевагу цьому посиланню для Javadoc . І +1 тому, що одна менша залежність!
Франклін Ю

3
Де entryзадокументовано Java 9 ?
nobar

1029

Це один із способів.

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

Однак ви повинні бути обережними і переконайтеся, що ви розумієте вищевказаний код (він створює новий клас, який успадковується від HashMap). Тому слід прочитати більше тут: http://www.c2.com/cgi/wiki?DoubleBraceInitialization або просто скористатися Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);

72
Це працює, але це некрасиво і має невидимі побічні ефекти, які користувач повинен зрозуміти, перш ніж робити це - наприклад, генеруючи на місці весь анонімний клас.
jprete

96
так, саме так я писав про обережність і дав посилання на опис.
gregory561

6
Чудове посилання. Посилання в цьому посиланні на GreencoddsTenthRuleOfProgramming варто прочитати.
michaelok

19
ви можете додати "як ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()", оскільки метод "of" обмежений максимум 5 парами?
kommradHomer


341

Якщо ви дозволяєте 3rd Party LIBS, ви можете використовувати Guava «s ImmutableMap для досягнення буквальним , як стислість:

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

Це працює до 5 пар ключ / значення , в іншому випадку ви можете використовувати його будівельник :

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


  • зауважте, що реалізація ImmutableMap Guava відрізняється від Java HashMap реалізації ( в першу чергу це незмінна і не допускає нульових ключів / значень)
  • для отримання додаткової інформації дивіться статтю в посібнику користувача Guava про його непорушні типи колекції

26
Також guava має ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Ксетій

17
ImmutableMap - це не те саме, що HashMap, оскільки він не зможе застосувати значення нульових значень, тоді як карта HashMap не буде.
Gewthen

2
Просто, щоб допомогти іншим, хто може зіткнутися з цією проблемою. Ви повинні ввести конструктора, щоб він став Map <String, String>, наприклад: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Тіаго

це дивовижний Єнс!
gaurav

105

Немає прямого способу зробити це - у Java немає літеральних карт (все ж - я думаю, вони були запропоновані для Java 8).

Деякі люди люблять це:

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

Це створює анонімний підклас HashMap, ініціалізатор екземпляра якого ставить ці значення. (До речі, карта не може містити вдвічі одного і того ж значення. Друге місце буде замінено першим. Я буду використовувати різні значення для наступних прикладів.)

Нормальним способом було б це (для локальної змінної):

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

Якщо ваша testкарта є змінною екземпляра, поставте ініціалізацію в конструктор або ініціалізатор примірника:

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

Якщо ваша testкарта є змінною класу, поставте ініціалізацію в статичний ініціалізатор:

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

Якщо ви хочете, щоб ваша карта ніколи не змінювалася, вам слід після ініціалізації обгорнути карту Collections.unmodifiableMap(...). Ви також можете це зробити в статичному ініціалізаторі:

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

(Я не впевнений, чи можете ви зараз зробити testостаточний ... спробуйте це і повідомте тут.)


61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};

Просте і суттєве. Я думаю, що це найкращий варіант відповіді з розширеним коментарем.
ooolala

15
Існують наслідки пам'яті, які слід зазначити. blog.jooq.org/2014/12/08/…
Амальговінус

1
@Amalgovinus В основному, створюючи новий підклас, ви жорстко кодуєте аргументи типу з HashMapцього підкласу. Це може працювати лише в тому випадку, якщо ви їх фактично надаєте. (З новим (порожнім) HashMap аргументи типу не мають значення.)
Paŭlo Ebermann

1
Мені подобається чистота цього, але він створює непотрібний анонімний клас і має тут описані проблеми: c2.com/cgi/wiki?DoubleBraceInitialization
udachny

1
@hello_its_me: Тому що його так само , як stackoverflow.com/a/6802512/1386911 відповідь, тільки форматування різні. І в цьому випадку це розширене форматування не має додаткової цінності поверх компактного формату для читабельності.
Даніель Харі

44

Альтернативно, використовуючи прості класи Java 7 і varargs: створити клас за HashMapBuilderдопомогою цього методу:

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

Використовуйте такий метод:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");

Я написав відповідь , натхненний твоїм: stackoverflow.com/questions/507602 / ...
GeroldBroser Моніка відновлює

1
Ще одне рішення з Apache Utils, яке ніколи не згадується, але читається, використовуючи попередні версії Java: MapUtils.putAll (новий HashMap <String, String> (), новий Object [] {"Мій ключ", "Моє значення", ...
Ролінтокур

3

тл; д-р

Використовуйте Map.of…методи у Java 9 та новіших версіях.

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

Map.of

Java 9 додала низку Map.ofстатичних методів для того, щоб робити саме те, що вам завгодно: Вмикайте незмінний Mapза допомогою буквального синтаксису .

Карта (колекція записів) є незмінною, тому ви не можете додавати або видаляти записи після інстанції. Також ключ і значення кожного запису незмінні, неможливо змінити. Дивіться у Javadoc інші правила, такі як заборонено використовувати NULL, не допускаються повторювані ключі, а порядок ітерації відображень довільний.

Давайте розглянемо ці методи, використовуючи деякі зразкові дані для карти дня тижня для людини, яка, як ми очікуємо, буде працювати в цей день.

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

Map.of()

Map.ofстворює порожнє Map. Немодифікується, тому ви не можете додавати записи. Ось приклад такої карти, порожній без записів.

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

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)є декілька методів, які беруть від 1 до 10 пар ключових значень. Ось приклад двох записів.

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

weekendWorker.toString (): {SUNDAY = Особа {name = 'Bob'}, SATURDAY = Особа {name = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )приймає будь-яку кількість об'єктів, що реалізують Map.Entryінтерфейс. Java пов'язує два класи , що реалізують цей інтерфейс, один змінюваних, інші незмінний: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Але конкретного класу нам не потрібно вказувати. Нам просто потрібно зателефонувати за Map.entry( k , v )методом виклику , передати свій ключ і наше значення, і ми повернемо об'єкт Map.Entryінтерфейсу, який реалізує якийсь клас .

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 (): {WEDNESDAY = Людина {name = 'Bob'}, TUESDAY = Людина {name = 'Bob'}, ЧЕТВЕРЖЕННЯ = Особа {name = 'Carol'}, ПЕТЯЦЬ = Особа {name = 'Carol'} , ПОНЕДІЛК = Особа {name = 'Alice'}}

Map.copyOf

Java 10 додала метод Map.copyOf. Передайте наявну карту, отримайте непорушну копію карти.

Примітки

Зауважте, що порядок ітератора карт, виготовлених через Map.of, не гарантується. Записи мають довільний порядок. Не пишіть код на основі баченого замовлення, оскільки документація попереджає, що замовлення може бути змінено.

Зверніть увагу , що всі ці Map.of…методи повертають Mapз невизначеного класу . Основи конкретного класу можуть навіть відрізнятися від однієї версії Java до іншої. Ця анонімність дозволяє Java вибирати з різних реалізацій, що б оптимально відповідало вашим конкретним даним. Наприклад, якщо ваші ключі надходять із enum , Java може використовувати EnumMapпід кришками.


1

Можливо, ви можете Map.ofлегко створити свій власний (який доступний лише у Java 9 та новіших версій) метод двома простими способами

Зробіть це із заданою кількістю параметрів

Приклад

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

Зробіть це за допомогою списку

Ви також можете зробити це за допомогою списку, замість того, щоб робити безліч методів для певного набору параметрів.

Приклад

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

Примітка. Не рекомендується використовувати це для всього, оскільки це робить анонімний клас кожного разу, коли ви користуєтесь цим.


1

JAVA 8

У звичайній java 8 у вас також є можливість використовувати Streams/Collectorsдля виконання завдання.

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

Це має перевагу в тому, що не створювати клас Anonymous.

Зауважте, що імпорт:

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

Звичайно, як зазначається в інших відповідях, у java 9 і далі у вас є більш прості способи зробити те саме.


0

На жаль, використання varargs, якщо тип ключів і значень не однаковий, не дуже розумно, як вам доведеться використовувати Object...і повністю втратити безпеку типу. Якщо ви завжди хочете створити, наприклад, a Map<String, String>, звичайно, це toMap(String... args)було б можливо, але не дуже красиво, оскільки було б легко змішати ключі та значення, а непарна кількість аргументів була б недійсною.

Ви можете створити підклас HashMap, який має подібний метод

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

і використовувати його як new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Інший підхід полягає у використанні загальної моделі будівельника:

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

і використовувати його як new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Однак рішення, яке я використовував раз у раз, використовує varargs та Pairclass:

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

Багатослівність Pair.create()мене трохи турбує, але це працює цілком чудово. Якщо ви не заперечуєте проти статичного імпорту, ви, звичайно, можете створити помічник:

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

(Замість цього Pairможна було б використовувати Map.Entry, але оскільки це інтерфейс, він вимагає клас реалізації та / або заводський метод помічника. Він також не є незмінним і містить іншу логіку, не корисну для цього завдання.)


0

Ви можете використовувати потоки в Java 8 (це зразок набору):

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

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

Довідка: https://www.baeldung.com/java-double-brace-initialization


0

Якщо вам потрібно розмістити лише одну пару ключ-значення, ви можете використовувати Collections.singletonMap (ключ, значення);


1
Форматування коду робить публікацію значно читабельнішою
Ренато
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.