Как я могу инициализировать статическую карту?

940

Как бы вы инициализировали статическую Map в Java?

Способ первый: статический инициализатор
Способ второй: инициализатор экземпляра (анонимный подкласс) или какой-то другой метод?

Каковы плюсы и минусы каждого?

Вот пример, иллюстрирующий два метода:

import java.util.HashMap;
import java.util.Map;

public class Test {
    private static final Map<Integer, String> myMap = new HashMap<Integer, String>();
    static {
        myMap.put(1, "one");
        myMap.put(2, "two");
    }

    private static final Map<Integer, String> myMap2 = new HashMap<Integer, String>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}
  • 2
    Для инициализации карты в Java 8: stackoverflow.com/a/37384773/1216775
  • 2
    Пожалуйста, никогда не используйте двойную инициализацию - это взлом, простой способ утечки памяти и другие проблемы.
Показать ещё 1 комментарий
Теги:
dictionary
collections
initialization
idiomatic

41 ответ

941
Лучший ответ

Инициализатор экземпляра - это просто синтаксический сахар в этом случае, верно? Я не понимаю, зачем нужен дополнительный анонимный класс для инициализации. И это не сработает, если создаваемый класс является окончательным.

Вы также можете создать неизменяемую карту, используя статический инициализатор:

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);
    }
}
  • 9
    Это идиома, которую я использовал годами, и я никогда не видел, чтобы кто-то смотрел на нее. Я делаю то же самое для неизменяемых константных множеств и списков тоже.
  • 3
    Как бы я обработал HashMap <String, String> с ключом String. Объект Map не позволяет мне иметь ключ String, поэтому я не могу использовать unmodifiableMap (). Я предполагаю, что приведение к HashMap также победит цель. Есть идеи?
Показать ещё 7 комментариев
392

Мне нравится Guava способ инициализации статической, неизменяемой карты:

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

Как вы можете видеть, это очень красноречиво (из-за удобных методов factory в ImmutableMap).

Если вы хотите, чтобы карта имела более 5 записей, вы больше не можете использовать ImmutableMap.of(). Вместо этого попробуйте ImmutableMap.builder() в следующих строках:

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

Чтобы узнать больше о преимуществах утилит универсальной коллекции Guava, см. Неизменяемые коллекции, объясненные в Руководстве пользователя Guava.

(Подмножество) Гуава раньше называлось Коллекциями Google. Если вы еще не используете эту библиотеку в своем Java-проекте, я strong рекомендую попробовать! Guava быстро стал одной из самых популярных и полезных бесплатных сторонних библиотек для Java, поскольку пользователи SO SO согласны. (Если вы новичок в этом, за этой ссылкой есть отличные учебные ресурсы.)


Обновление (2015). Что касается Java 8, я бы по-прежнему использовал подход Guava, потому что он намного чище, чем что-либо еще. Если вам не нужна зависимость от Guava, рассмотрите простой старый метод init. Взлом с двумерным массивом и Stream API довольно уродлив, если вы спросите меня и получите более уродливое, если вам нужно создать карту, ключи и значения которой не совпадают типа (например, Map<Integer, String> в вопросе).

Что касается будущего Guava в целом, то в отношении Java 8 Louis Wasserman сказал это еще в 2014 году и [обновление] в 2016 году было объявлено, что Guava 21 потребует и правильно поддерживает Java 8.


Обновление (2016): как указывает Тагир Валеев, Java 9, наконец, сделает это чистым не используя ничего, кроме чистого JDK, добавив удобные методы factory для коллекций:

static final Map<Integer, String> MY_MAP = Map.of(
    1, "one", 
    2, "two"
);
  • 20
    Похоже, что наши коллеги-администраторы удалили почтенный вопрос «Самые полезные бесплатные сторонние библиотеки Java», на который я ссылался. Черт их побери
  • 2
    Я согласен, это самый хороший способ инициализации постоянной карты. Не только более читабельно, но и так как Collections.unmodifiableMap возвращает доступное только для чтения представление базовой карты (которое все еще может быть изменено).
Показать ещё 6 комментариев
176

Я бы использовал:

public class Test {
    private static final Map<Integer, String> MY_MAP = createMap();

    private static Map<Integer, String> createMap() {
        Map<Integer, String> result = new HashMap<Integer, String>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  • он избегает анонимного класса, который я лично считаю плохим, и избегаю
  • он делает создание карты более явным
  • делает карту немодифицируемой
  • поскольку MY_MAP является константой, я бы назвал его константой
  • 2
    Из опций чистого JDK (без библиотек) мне это нравится больше всего, потому что определение карты явно связано с ее инициализацией. Также договорились о постоянном именовании.
155

Java 5 предоставляет этот более компактный синтаксис:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};
  • 40
    Эта техника называется инициализацией двойной скобкой: stackoverflow.com/questions/1372113/… Это не специальный синтаксис Java 5, это всего лишь уловка с анонимным классом с инициализатором экземпляра.
  • 13
    Быстрый вопрос относительно инициализации двойной скобки: при этом Eclipse выдает предупреждение об отсутствующем серийном идентификаторе. С одной стороны, я не понимаю, зачем нужен Serial ID в этом конкретном случае, но с другой стороны, я обычно не люблю подавлять предупреждения. Что вы думаете об этом?
Показать ещё 6 комментариев
92

Одним из преимуществ второго метода является то, что вы можете обернуть его с Collections.unmodifiableMap() чтобы гарантировать, что ничто не собирается обновлять коллекцию позже:

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
    Разве вы не можете легко сделать это в первом методе, переместив оператор new в статический блок {} и обернув его?
  • 2
    В любом случае, я бы переместил вызов конструктора в инициализированный static. Все остальное просто выглядит странно.
Показать ещё 2 комментария
59

Вот инициализатор статической карты 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]));

Изменение: чтобы инициализировать Map<Integer, String> как в вопросе, вам нужно что-то вроде этого:

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

Редактирование (2): существует лучшая версия с поддержкой смешанного типа от i_am_zero, которая использует поток new SimpleEntry<>(k, v). Проверьте этот ответ: https://stackoverflow.com/questions/507602/how-can-i-initialise-a-static-map

  • 6
    Я взял на себя смелость добавить версию, эквивалентную вопросу и другим ответам: инициировать карту, ключи и значения которой имеют другой тип (поэтому String[][] не подойдет, Object[][] необходим). ИМХО, этот подход уродлив (тем более с кастами) и его трудно запомнить; не буду использовать это сам.
  • 2
    Для инициализации карты в Java 8: stackoverflow.com/a/37384773/1216775
48

В Java 9:

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

См. JEP 269 для деталей. JDK 9 поступил в продажу в сентябре 2017 года.

  • 5
    Или, если вам нужно более 10 пар ключ-значение, вы можете использовать Map.ofEntries
  • 5
    Это чисто и все, пока не поймешь, как это было реализовано
Показать ещё 2 комментария
29

С Коллекции Eclipse (ранее Коллекции GS), все из них будет работать:

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

Вы также можете статически инициализировать примитивные карты с коллекциями Eclipse.

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

Примечание: Я являюсь коммиттером для коллекций Eclipse

26

Java 9

Мы можем использовать Map.ofEntries как:

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

Мы также можем использовать Map.of как предложил Тагир в своем ответе здесь, но мы не можем иметь более 10 записей, использующих Map.of

Java 8 (аккуратное решение)

Мы можем создать поток записей на карте. У нас уже есть две реализации Entry в java.util.AbstractMap которые являются SimpleEntry и SimpleImmutableEntry. Для этого примера мы можем использовать первый как:

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));
  • 0
    new SimpleEntry<>() гораздо менее читабелен, чем static put() : /
25

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

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
    В какой ситуации вы бы использовали анонимный подкласс для инициализации hashmap?
  • 6
    Никогда не инициализировать коллекцию.
Показать ещё 4 комментария
16

Мне нравится анонимный класс, потому что с ним легко справиться:

public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
    {
        put(1, "some value");
                    //rest of code here
    }
});
16

Возможно, интересно посмотреть Google Collections, например. видео, которое они имеют на своей странице. Они предоставляют различные способы инициализации карт и наборов, а также предоставляют неизменные коллекции.

Обновление: эта библиотека теперь называется Guava.

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

Если мы объявим более одной константы, тогда этот код будет записан в статическом блоке, и в будущем это будет сложно поддерживать. Поэтому лучше использовать анонимный класс.

public class Test {

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

И предлагается использовать unmodifiableMap для констант, иначе он не может считаться постоянным.

10

Я мог бы настоятельно предложить стиль "двойного выравнивания фигур" в стиле статического блока.

Кто-то может прокомментировать, что они не любят анонимный класс, накладные расходы, производительность и т.д.

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

  • Элементы являются вложенными и встроенными.
  • Это больше OO, а не процедурный.
  • влияние производительности действительно мало и может быть проигнорировано.
  • Лучшая поддержка контура IDE (а не много анонимных статических {} блоков)
  • Вы сохранили несколько строк комментария, чтобы принести им отношения.
  • Предотвращение утечки элемента/экземпляра элемента неинициализированного объекта из исключения и оптимизатора байт-кода.
  • Не беспокойтесь о порядке выполнения статического блока.

Кроме того, вы знаете GC анонимного класса, вы всегда можете преобразовать его в обычный HashMap с помощью new HashMap(Map map).

Вы можете сделать это, пока не столкнетесь с другой проблемой. Если вы это сделаете, вы должны использовать для этого еще один стиль кодирования (например, не статический класс factory).

8

Как обычно, apache-commons имеет правильный метод MapUtils.putAll(Map, Object []):

Например, чтобы создать цветовую карту:

Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
     {"RED", "#FF0000"},
     {"GREEN", "#00FF00"},
     {"BLUE", "#0000FF"}
 });
  • 0
    Я включаю Apache Commons во все сборки, поэтому, к сожалению, при отсутствии метода Arrays.asMap( ... ) в простой Java я думаю, что это лучшее решение. Изобретать колесо обычно глупо. Очень слабым минусом является то, что с дженериками потребуется непроверенное преобразование.
  • 0
    Версия @mikerodent 4.1 является общей: общедоступная статическая <K, V> карта <K, V> putAll (окончательная карта <K, V> карта, конечный массив Object [])
Показать ещё 6 комментариев
6

Если вы хотите немодифицируемую карту, наконец, в java 9 добавлен класс factory of to Map. Аналогичный метод добавляется и в Set, List.

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

6

Здесь мой любимый, когда я не хочу (или не могу) использовать Guava ImmutableMap.of(), или если мне нужен изменяемый 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]);
        }
    }};
}

Он очень компактен и игнорирует паразитные значения (т.е. конечный ключ без значения).

Использование:

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

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

Примечание:. В вопросе ничего не говорится о том, чтобы сделать карту немодифицируемой, поэтому я оставлю это, но знаю, что ее можно легко сделать с помощью Collections.unmodifiableMap(map).

Первый совет

Первый совет: вы можете сделать локальную ссылку на карту, и вы дадите ей короткое имя:

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

Второй совет

Второй совет: вы можете создать вспомогательный метод для добавления записей; вы также можете сделать этот вспомогательный метод общедоступным, если хотите:

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

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

Третий отзыв

Третий совет состоит в том, что вы можете создать класс-помощник, который можно использовать повторно, с помощью заполняющих функций. Это действительно простой, 10-строчный вспомогательный класс, который безопасен по типу:

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

Создаваемый вами анонимный класс хорошо работает. Однако вы должны знать, что это внутренний класс и поэтому он будет содержать ссылку на экземпляр окружающего класса. Таким образом, вы обнаружите, что не можете делать с ним определенные вещи (используя XStream для одного). Вы получите очень странные ошибки.

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

РЕДАКТИРОВАТЬ: правильно отметил в комментариях, что это статический класс. Очевидно, я не читал это достаточно внимательно. Однако мои комментарии все еще применимы к анонимным внутренним классам.

  • 3
    В данном конкретном случае это статично, поэтому нет внешнего экземпляра.
  • 0
    Возможно, XStream не должен пытаться сериализовать подобные вещи (это статично. Зачем вам нужно сериализовать статическую переменную?)
Показать ещё 1 комментарий
4

Вы можете использовать StickyMap и MapEntry from Cactoos:

private static final Map<String, String> MAP = new StickyMap<>(
  new MapEntry<>("name", "Jeffrey"),
  new MapEntry<>("age", "35")
);
4

С Java 8 я пришел к использованию следующего шаблона:

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

Это не самый короткий и немного окольный, но

  • он не требует ничего вне java.util
  • он типирует и легко вмещает разные типы ключей и значений.
  • 0
    при необходимости можно использовать сигнатуру toMap включая поставщика карт, чтобы указать тип карты.
4

Если вам нужно что-то кратное и относительно безопасное, вы можете просто переключить проверку типа времени компиляции на время выполнения:

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

Эта реализация должна улавливать любые ошибки:

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

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

Другой простой способ инициализации статической карты - использование этой служебной функции:

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

Примечание: в Java 9 вы можете использовать Map.of

3

Поскольку Java не поддерживает картографические литералы, экземпляры карт всегда должны быть явно созданы и заполнены.

К счастью, с помощью методов factory можно аппроксимировать поведение литералов в Java.

Например:

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

Вывод:

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

Это намного удобнее, чем создание и заполнение карты элементом за раз.

3

Я не видел того подхода, который я использую (и стал любить), размещенного в любых ответах, так что вот оно:

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

вместо этого, я предпочитаю инициализацию, которая выглядит так:

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

К сожалению, эти методы не являются частью стандартной библиотеки Java, поэтому вам нужно будет создать (или использовать) библиотеку утилиты, которая определяет следующие методы:

 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)

(вы можете использовать "import static", чтобы избежать необходимости префикса имени метода)

Мне было полезно предоставить аналогичные статические методы для других коллекций (список, набор, sortedSet, sortedMap и т.д.)

Это не так хорошо, как инициализация объекта json, но это шаг в этом направлении, насколько читаемость.

3

Мне не нравится синтаксис статического инициализатора, и я не уверен в анонимных подклассах. Как правило, я согласен со всеми минусами использования статических инициализаторов и всех недостатков использования анонимных подклассов, которые были упомянуты в ответах предыдущего. С другой стороны - профессионалов, представленных на этих постах, недостаточно для меня. Я предпочитаю использовать статический метод инициализации:

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

Ну... Мне нравятся перечисления;)

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

Если вам нужно добавить только одно значение на карту, вы можете использовать Collections.singletonMap:

Map<K, V> map = Collections.singletonMap(key, value)
2

JEP 269 предоставляет некоторые удобные методы factory для API коллекций. Эти методы factory не находятся в текущей версии Java, которая составляет 8, но планируются для выпуска Java 9.

Для Map существуют два метода factory: of и ofEntries. Используя of, вы можете передавать пары переменных/ключей. Например, чтобы создать Map как {age: 27, major: cs}:

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

В настоящее время существует десять перегруженных версий для of, поэтому вы можете создать карту, содержащую десять пар ключ/значение. Если вам не нравится это ограничение или переменный ключ/значения, вы можете использовать ofEntries:

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

Оба of и ofEntries возвращают неизменяемый Map, поэтому вы не сможете изменить их элементы после построения. Вы можете попробовать эти функции, используя JDK 9 Early Access.

2

Я прочитал ответы и решил написать свой собственный конструктор карт. Не стесняйтесь копировать-вставить и наслаждаться.

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

EDIT: В последнее время я продолжаю находить общественный статический метод of довольно часто, и я вроде как он. Я добавил его в код и сделал конструктор приватным, переключившись на статический шаблон метода фабрики.

Не EDIT2: Еще совсем недавно, я уже не как статический метод, называемый of, как это выглядит довольно плохо при использовании статического импорта. mapOf этого я переименовал его в mapOf, что сделало его более подходящим для статического импорта.

0

Примечание. Этот ответ на самом деле относится к вопросу Как напрямую инициализировать HashMap (в буквальном смысле)? но так как он помечен как [дубликат] этого...


До Java 9 с его Map.of() (который также ограничен 10 отображениями) вы можете расширить реализацию Map по вашему выбору, например:

public class InitHashMap<K, V> extends HashMap<K, V>

повторно реализовать конструкторы HashMap:

public InitHashMap() {
    super();
}

public InitHashMap( int initialCapacity, float loadFactor ) {
    super( initialCapacity, loadFactor );
}

public InitHashMap( int initialCapacity ) {
    super( initialCapacity );
}

public InitHashMap( Map<? extends K, ? extends V> m ) {
    super( m );
}

и добавьте дополнительный конструктор, который вдохновлен ответом Aerthel, но является общим с использованием типов Object... и <K, V>:

public InitHashMap( final Object... keyValuePairs ) {

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

    K key = null;
    int i = -1;

    for ( final Object keyOrValue : keyValuePairs )
        switch ( ++i % 2 ) {
            case 0:  // key
                if ( keyOrValue == null )
                    throw new IllegalArgumentException( "Key[" + (i >> 1) + "] is <null>." );
                key = (K) keyOrValue;
                continue;
            case 1:  // value
                put( key, (V) keyOrValue );
        }
}

Бежать

public static void main( final String[] args ) {

    final Map<Integer, String> map = new InitHashMap<>( 1, "First", 2, "Second", 3, "Third" );
    System.out.println( map );
}

Выход

{1=First, 2=Second, 3=Third}

Также вы можете расширить интерфейс Map:

public interface InitMap<K, V> extends Map<K, V> {

    static <K, V> Map<K, V> of( final Object... keyValuePairs ) {

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

        final Map<K, V> map = new HashMap<>( keyValuePairs.length >> 1, .75f );
        K key = null;
        int i = -1;

        for ( final Object keyOrValue : keyValuePairs )
            switch ( ++i % 2 ) {
                case 0: // key
                    if ( keyOrValue == null )
                        throw new IllegalArgumentException( "Key[" + (i >> 1) + "] is <null>." );
                    key = (K) keyOrValue;
                    continue;
                case 1: // value
                    map.put( key, (V) keyOrValue );
            }
        return map;
    }
}

Бежать

public static void main( final String[] args ) {

    System.out.println( InitMap.of( 1, "First", 2, "Second", 3, "Third" ) );
}

Выход

{1=First, 2=Second, 3=Third}
0

Даже с хорошим классом ImmutableMap от Guava, иногда мне хотелось бы создать изменчивую карту. Обнаружив, что я хочу избежать статических блоков и анонимного подтипа, когда пришла Java 8, я написал небольшую библиотеку, которая помогла вам вызвать Fluent.

// simple usage, assuming someMap is a Map<String, String> already declared
Map<String, String> example = new Fluent.HashMap<String, String>()
    .append("key1", "val1")
    .append("key2", "val2")
    .appendAll(someMap);

При использовании интерфейса Java 8 по умолчанию я мог бы реализовать методы Fluent.Map для всех стандартных реализаций Java Map (т.е. HashMap, ConcurrentSkipListMap и т.д.) без утомительного повторения.

Немодифицируемые карты тоже просты.

Map<String, Integer> immutable = new Fluent.LinkedHashMap<String, Integer>()
    .append("one", 1)
    .append("two", 2)
    .append("three", 3)
    .unmodifiable();

См. https://github.com/alexheretic/fluent для источника, документации и примеров.

0

Вот код от AbacusUtil

Map<Integer, String> map = N.asMap(1, "one", 2, "two");
// Or for Immutable map 
ImmutableMap<Integer, String> = ImmutableMap.of(1, "one", 2, "two");

Декларация: я разработчик AbacusUtil.

0

Здесь есть несколько хороших ответов, но я хочу предложить еще один.

Создайте свой собственный статический метод для создания и инициализации Map. У меня есть собственный класс CollectionUtils в пакете, который я использую в проектах с различными утилитами, которые я использую регулярно, что легко писать и избегает необходимости в зависимости от какой-либо более крупной библиотеки.

Здесь мой метод newMap:

public class CollectionUtils {
    public static Map newMap(Object... keyValuePairs) {
        Map map = new HashMap();
        if ( keyValuePairs.length % 2 == 1 ) throw new IllegalArgumentException("Must have even number of arguments");
        for ( int i=0; i<keyValuePairs.length; i+=2 ) {
            map.put(keyValuePairs[i], keyValuePairs[i + 1]);
        }
        return map;
    }
}

Использование:

import static CollectionUtils.newMap;
// ...
Map aMap = newMap("key1", 1.23, "key2", 2.34);
Map bMap = newMap(objKey1, objVal1, objKey2, objVal2, objKey3, objVal3);
// etc...

Он не использует generics, но вы можете придать тип карте по своему усмотрению (просто убедитесь, что вы правильно ее вывели!)

Map<String,Double> aMap = (Map<String,Double>)newMap("key1", 1.23, "key2", 2.34);
0

В Java 8 процедурный подход также может быть заключен в Supplier:

Map<String,String> m = ((Supplier<Map<String,String>>)(() -> {
    Map<String,String> result = new HashMap<>();
    result.put("foo","hoo");
    ...
    return result;
)).get();

Это только гипотетический способ, но может пригодиться, если вам действительно нужен один-лайнер.

0

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

Например:

public abstract class Shape {

    public static final String COLOR_KEY = "color_key";
    public static final String OPAQUE_KEY = "opaque_key";

    private final String color;
    private final Boolean opaque;

    /**
     * Initializing constructor - note no default constructor.
     *
     * @param properties a collection of Shape properties
     */
    public Shape(Map<String, Object> properties) {
        color = ((String) properties.getOrDefault(COLOR_KEY, "black"));
        opaque = (Boolean) properties.getOrDefault(OPAQUE_KEY, false);
    }

    /**
     * Color property accessor method.
     *
     * @return the color of this Shape
     */
    public String getColor() {
        return color;
    }

    /**
     * Opaque property accessor method.
     *
     * @return true if this Shape is opaque, false otherwise
     */
    public Boolean isOpaque() {
        return opaque;
    }
}

и моя конкретная реализация этого класса, но он хочет/нуждается в конструкторе по умолчанию:

public class SquareShapeImpl extends Shape {

    private static final Map<String, Object> DEFAULT_PROPS = new HashMap<>();

    static {
        DEFAULT_PROPS.put(Shape.COLOR_KEY, "yellow");
        DEFAULT_PROPS.put(Shape.OPAQUE_KEY, false);
    }

    /**
     * Default constructor -- intializes this square to be a translucent yellow
     */
    public SquareShapeImpl() {
        // the static initializer was useful here because the call to 
        // this(...) must be the first statement in this constructor
        // i.e., we can't be mucking around and creating a map here
        this(DEFAULT_PROPS);
    }

    /**
     * Initializing constructor -- create a Square with the given
     * collection of properties.
     *
     * @param props a collection of properties for this SquareShapeImpl
     */
    public SquareShapeImpl(Map<String, Object> props) {
        super(props);
    }
}

то для использования этого конструктора по умолчанию мы просто делаем:

public class StaticInitDemo {

    public static void main(String[] args) {

        // create a translucent, yellow square...
        Shape defaultSquare = new SquareShapeImpl();

        // etc...
    }
}
0

Теперь, когда Java 8 отсутствует, этот вопрос требует пересмотра. Я взял на него удар - похоже, вы можете использовать синтаксис выражений лямбда, чтобы получить довольно красивый и сжатый (но безопасный тип) буквенный синтаксис карты, который выглядит так:

        Map<String,Object> myMap = hashMap(
                bob -> 5,
                TheGimp -> 8,
                incredibleKoolAid -> "James Taylor",
                heyArnold -> new Date()
        );

        Map<String,Integer> typesafeMap = treeMap(
                a -> 5,
                bee -> 8,
                sea -> 13
                deep -> 21
        );

Неподтвержденный образец кода https://gist.github.com/galdosd/10823529 Было бы любопытно мнение других об этом (это мягко зло...)

  • 1
    Примечание: Оказывается, после того, как вы попробовали это позже ... вышеприведенное не работает. Я попробовал это с несколькими различными флагами javac и не смог получить имя, используемое для параметра, который будет сохранен.
0

Если вы можете использовать строковое представление ваших данных, это тоже вариант в Java 8:

static Map<Integer, String> MAP = Stream.of(
        "1=one",
        "2=two"
).collect(Collectors.toMap(k -> Integer.parseInt(k.split("=")[0]), v -> v.split("=")[1]));
0

Я сделал что-то совсем другое. Не самое лучшее, но это работает для меня. Возможно, он может быть "обобщен".

private static final Object[][] ENTRIES =
{
  {new Integer(1), "one"},
  {new Integer(2), "two"},
};
private static final Map myMap = newMap(ENTRIES);

private static Map newMap(Object[][] entries)
{
  Map map = new HashMap();

  for (int x = 0; x < entries.length; x++)
  {
    Object[] entry = entries[x];

    map.put(entry[0], entry[1]);
  }

  return map;
}
  • 0
    Мне нравится маленький код, необходимый для инициализации карт таким образом
  • 0
    Проблема этого подхода заключается в том, что он вообще не является типобезопасным (а если вы используете Java, вам нужна безопасность типов). Вы можете поместить любой вид объектов в качестве ключей и значений. Это действительно работает только для Map<Object, Object> (хотя можно использовать аналогичный подход с String[][] для Map<String,String> и аналогичным для других Map<T,T> . Это не работает для карт, где тип ключа отличается от типа значения.
0

Мне нравится синтаксис анонимного класса; это просто меньше кода. Тем не менее, один из основных я обнаружил, что вы не сможете сериализовать этот объект путем удаленного доступа. Вы получите исключение из-за невозможности найти анонимный класс на удаленной стороне.

  • 0
    Вы можете создать карту, используя идиому с двойными скобками, а затем скопировать ее.
0

Второй метод может при необходимости использовать защищенные методы. Это может быть полезно для инициализации классов, неизменяемых после построения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню