Java HashMap - Нужно ли использовать .put() для каждого набора?

Представьте себе следующий сценарий. У вас есть набор значений, которые уже доступны и известны. Вы должны поместить их в HashMap по причинам.

Пример кода:

String a = "a";
Strinb b = "b";
...
HashMap map = new HashMap(5);
map.put("a", a);
map.put("b", b);
...

Неужели так нужно делать это? Я не могу поверить, что нет конструктора, который позволяет вам вводить ваши ценности с нуля.

Я говорю о чем-то вроде этого:

HashMap map = new HashMap(<"a", a>, <"b", b>, ...);

Или даже что-то вроде этого:

HashMap map = new HashMap(5);
HashMap.putBunchOfStuff("a", a, "b", b, ...);

Изменить: Мое намерение состояло в том, чтобы спросить, есть ли метод, но что более важно, , если ответ "нет", почему нет такого конструктора/метода.

Ответ 1

К сожалению, коллекционные литералы были предложением для Project Coin в Java 7 (и Java 8), но он никогда не попадал в конечный продукт, иначе он не является признаком Java.

Предложение состояло в этом

Here’s how it would look with map literals:


    final Map<Integer, String> platonicSolids = { 
          4 : "tetrahedron",
          6 : "cube", 
          8 : "octahedron", 
          12 : "dodecahedron", 
          20 : "icosahedron"
    };


Here is the empty map literal, which has a slightly irregular syntax to make
it differ from the empty set:


    Map<String, Integer> noJokeHere = { : };

НО это никогда не случалось, поэтому, к сожалению, это не сработало. Поэтому, если вы не написали свой собственный волшебный строитель или фантазию лямбда, например на этом сайте Per-Åke Minborg, вы сами по себе. Следующие из сайта должны работать, хотя (в Java 8).

//copy paste from linked site
Map<Integer, String> map = Stream.of(
            new SimpleEntry<>(0, "zero"),
            new SimpleEntry<>(1, "one"),
            //...
            new SimpleEntry<>(11, "eleven"),
            new SimpleEntry<>(12, "twelve"))
            .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));

И упрощенная версия, также из сайта:

//copy paste from linked site
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
    return new AbstractMap.SimpleEntry<>(key, value);
}

public static <K, U> Collector<Map.Entry<K, U>, ?, Map<K, U>> entriesToMap() {
    return Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue());
}

Map<Integer, String> map = Stream.of(
            entry(0, "zero"),
            //...
            entry(12, "twelve"))
            .collect(entriesToMap());

Литералы коллекции не были введены из-за этих пунктов:

  • "Простая" версия этой функции (наборы, списки, только карты) не является очень удовлетворительный или популярный; "расширяемая" версия этой функции открытый, грязный и практически гарантированный способ преодоления его дизайна бюджет;

  • Библиотека на основе версии дает нам X% выгоды для 1% стоимость, где X → 1;

  • Приводятся типы значений и "что будет выглядеть эта функция" в мире со значениями типов вполне может быть совершенно иным, чем в мире без, предполагая, что было бы сомнительно попытаться выполнить эту работу до типов значений;

  • Нам лучше сосредоточиться на решая больше основополагающих проблем, лежащих в основе версии (в том числе: более эффективные varargs, константы массива в постоянный пул, неизменяемые массивы и поддержка кеширования (и восстановления под давлением) промежуточные неизменные результаты).

Брайан Гетц из Oracle

Ответ 2

По умолчанию не существует такого конструктора - вы могли бы использовать двойную привязку к put всем значениям на карте при создании экземпляра, но для этого все еще требуются несколько вызовов:

Map<String,String> map = new HashMap<>() {{
    put("a", "val1");
    put("b", "val2");
    //etc.
}};

В действительности у вас обычно есть большие блоки данных, которые вы хотите поместить в карту, доступную из потока или другой коллекции, поэтому тривиально перебирать несколько вызовов put в этом экземпляре.

Guava имеет putAll() на своем ImmutableMap.Builder, позволяя вам сделать что-то похожее на это до создания экземпляра. Другие внешние библиотеки могут иметь другое сходное поведение, но факт заключается в том, что вам придется прибегать к использованию внешних библиотек.

Ответ 3

Можно создать довольно полезный метод для создания карт с использованием varargs.

Пример:

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> newMap(Object... elems) {
    HashMap<K, V> m = new HashMap<K, V>();

    for (int i = 0; i < elems.length; i += 2) {
        m.put((K) elems[i], (V) elems[i + 1]);    
    }

    return m;
}

Map<String, Integer> m = newMap("k1", 1, "k2", 2);        

Остерегайтесь:. Это не безопасный тип и может привести к карте с объектами неправильных типов, если вы допустили ошибку при ее использовании, выбросив ClassCastException в неожиданные места. Это называется загрязнение кучи. Пример:

Map<String, Integer> m = newMap("k1", 1, "k2", "2");
int s = m.get("k2");  // ClassCastException!

Ответ 4

Вы можете использовать что-то вроде этого:

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

Но я не знаю, можно ли использовать u с локальной переменной a и b.