Как напрямую инициализировать HashMap (в буквальном смысле)?


1096

Есть ли способ инициализации 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.

Ответы:


1347

Все версии

На случай, если вам понадобится всего одна запись: есть 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
@ Майкл: Да, если вы хотите использовать функцию, вы не можете использовать не-функцию. Но почему ты хочешь?
Янки

6
и для случаев, когда вам нужна карта с одной записью, есть Collections.singletonMap():)
skwisgaar

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

3
Где entryдокументирована Java 9 ?
Нобар

1032

Это один из способов.

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
Вы можете добавить "as ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()", так как метод "of" ограничен максимум 5 парами?
kommradHomer


342

Если вы разрешаете сторонним библиотекам, вы можете использовать Gumm '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 реализации отличается от Явы HashMap реализации ( в первую очередь это неизменная и не допускает нулевых ключей / значений)
  • Для получения дополнительной информации см. статью руководства Guava о ее неизменных типах коллекций.

26
Кроме того, в guava есть ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius

17
ImmutableMap - это не то же самое, что HashMap, поскольку он потерпит неудачу при нулевых значениях, тогда как map HashMap - нет.
Gewthen

2
Просто чтобы помочь другим, которые могут столкнуться с этой проблемой. Вы должны ввести конструктор, чтобы сделать его Map <String, String>, например: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Тьяго

это классно, Дженс!
Гаурав

105

Нет прямого способа сделать это - у Java нет литералов Map (пока - я думаю, что они были предложены для 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);
    }
};

Просто и по существу. Я думаю, что это с расширенным разделом комментариев будет лучшим ответом.
ооолала

15
Есть некоторые последствия для памяти, которые следует отметить, хотя. blog.jooq.org/2014/12/08/…
Амальговинус

1
@Amalgovinus По сути, создав новый подкласс, вы жестко программируете аргументы типа из HashMapэтого подкласса. Это может работать, только если вы на самом деле их предоставили. (С новым (пустым) HashMap аргументы типа не имеют значения.)
Paŭlo Ebermann

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

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 (new HashMap <String, String> (), new Object [] {"Мой ключ", "мое значение", ...
Rolintocour

4

ТЛ; др

Используйте 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
        )
;

выходныеWorker.toString (): {SUNDAY = Person {name = 'Bob'}, SATURDAY = Person {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 = Person {name = 'Bob'}, TUESDAY = Person {name = 'Bob'}, THURSDAY = Person {name = 'Carol'}, FRIDAY = Person {name = 'Carol'} , ПОНЕДЕЛЬНИК = Персона {имя = 'Алиса'}}

Map.copyOf

Java 10 добавил метод Map.copyOf. Передайте существующую карту, верните неизменную копию этой карты.

Ноты

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

Обратите внимание , что все эти Map.of…методы возвращают Mapиз неопределенного класса . Базовый конкретный класс может даже варьироваться от одной версии Java к другой. Эта анонимность позволяет Java выбирать из различных реализаций, что бы ни подходило для ваших конкретных данных. Например, если ваши ключи взяты из перечисления , 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));

Это имеет преимущество в том, что не создает анонимный класс.

Обратите внимание, что импорт:

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

Конечно, как отмечалось в других ответах, в java 9 и далее у вас есть более простые способы сделать то же самое.


0

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

Вы можете создать подкласс 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 и Pairкласс:

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 (это пример Set):

@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 (key, value);


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