Как бы вы инициализировали статическую 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");
}
};
}
Инициализатор экземпляра - это просто синтаксический сахар в этом случае, верно? Я не понимаю, зачем нужен дополнительный анонимный класс для инициализации. И это не сработает, если создаваемый класс является окончательным.
Вы также можете создать неизменяемую карту, используя статический инициализатор:
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);
}
}
Мне нравится 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"
);
Я бы использовал:
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);
}
}
Java 5 предоставляет этот более компактный синтаксис:
static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
put("Up", "Down");
put("Charm", "Strange");
put("Top", "Bottom");
}};
Одним из преимуществ второго метода является то, что вы можете обернуть его с 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!
Вот инициализатор статической карты 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
String[][]
не подойдет, Object[][]
необходим). ИМХО, этот подход уродлив (тем более с кастами) и его трудно запомнить; не буду использовать это сам.
В Java 9:
private static final Map<Integer, String> MY_MAP = Map.of(1, "one", 2, "two");
См. JEP 269 для деталей. JDK 9 поступил в продажу в сентябре 2017 года.
Map.ofEntries
С Коллекции 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
Мы можем использовать 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
Мы можем создать поток записей на карте. У нас уже есть две реализации 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));
new SimpleEntry<>()
гораздо менее читабелен, чем static put()
: /
Я бы никогда не создал анонимный подкласс в этой ситуации. Статические инициализаторы работают одинаково хорошо, если вы хотите сделать карту немодифицируемой, например:
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);
}
Мне нравится анонимный класс, потому что с ним легко справиться:
public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
{
put(1, "some value");
//rest of code here
}
});
Возможно, интересно посмотреть Google Collections, например. видео, которое они имеют на своей странице. Они предоставляют различные способы инициализации карт и наборов, а также предоставляют неизменные коллекции.
Обновление: эта библиотека теперь называется Guava.
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 для констант, иначе он не может считаться постоянным.
Я мог бы настоятельно предложить стиль "двойного выравнивания фигур" в стиле статического блока.
Кто-то может прокомментировать, что они не любят анонимный класс, накладные расходы, производительность и т.д.
Но я больше рассматриваю читаемость кода и ремонтопригодность. В этой точке зрения, у меня есть двойная фигурная скобка, это скорее стиль кода, а не статический метод.
Кроме того, вы знаете GC анонимного класса, вы всегда можете преобразовать его в обычный HashMap с помощью new HashMap(Map map)
.
Вы можете сделать это, пока не столкнетесь с другой проблемой. Если вы это сделаете, вы должны использовать для этого еще один стиль кодирования (например, не статический класс factory).
Как обычно, apache-commons имеет правильный метод MapUtils.putAll(Map, Object []):
Например, чтобы создать цветовую карту:
Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
{"RED", "#FF0000"},
{"GREEN", "#00FF00"},
{"BLUE", "#0000FF"}
});
Arrays.asMap( ... )
в простой Java я думаю, что это лучшее решение. Изобретать колесо обычно глупо. Очень слабым минусом является то, что с дженериками потребуется непроверенное преобразование.
Если вы хотите немодифицируемую карту, наконец, в java 9 добавлен класс factory of
to Map
. Аналогичный метод добавляется и в Set, List.
Map<String, String> unmodifiableMap = Map.of("key1", "value1", "key2", "value2");
Здесь мой любимый, когда я не хочу (или не могу) использовать 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));
Я предпочитаю использовать статический инициализатор, чтобы избежать генерации анонимных классов (что не имело бы дальнейшей цели), поэтому я расскажу о советах, инициализирующихся статическим инициализатором. Все перечисленные решения/советы являются безопасными по типу.
Примечание:. В вопросе ничего не говорится о том, чтобы сделать карту немодифицируемой, поэтому я оставлю это, но знаю, что ее можно легко сделать с помощью 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
}
}
Создаваемый вами анонимный класс хорошо работает. Однако вы должны знать, что это внутренний класс и поэтому он будет содержать ссылку на экземпляр окружающего класса. Таким образом, вы обнаружите, что не можете делать с ним определенные вещи (используя XStream для одного). Вы получите очень странные ошибки.
Сказав это, до тех пор, пока вы знаете, тогда этот подход хорош. Я использую его большую часть времени для краткой инициализации всех видов коллекций.
РЕДАКТИРОВАТЬ: правильно отметил в комментариях, что это статический класс. Очевидно, я не читал это достаточно внимательно. Однако мои комментарии все еще применимы к анонимным внутренним классам.
С 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
toMap
включая поставщика карт, чтобы указать тип карты.
Если вам нужно что-то кратное и относительно безопасное, вы можете просто переключить проверку типа времени компиляции на время выполнения:
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);
}
}
}
Ваш второй подход (инициализация двойной скобки) считается анти-паттерном, поэтому я бы остановился на первом подходе.
Другой простой способ инициализации статической карты - использование этой служебной функции:
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
Поскольку 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}
Это намного удобнее, чем создание и заполнение карты элементом за раз.
Я не видел того подхода, который я использую (и стал любить), размещенного в любых ответах, так что вот оно:
Мне не нравится использовать статические инициализаторы, потому что они неуклюжи, и я не люблю анонимные классы, потому что он создает новый класс для каждого экземпляра.
вместо этого, я предпочитаю инициализацию, которая выглядит так:
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, но это шаг в этом направлении, насколько читаемость.
Мне не нравится синтаксис статического инициализатора, и я не уверен в анонимных подклассах. Как правило, я согласен со всеми минусами использования статических инициализаторов и всех недостатков использования анонимных подклассов, которые были упомянуты в ответах предыдущего. С другой стороны - профессионалов, представленных на этих постах, недостаточно для меня. Я предпочитаю использовать статический метод инициализации:
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;
}
}
Ну... Мне нравятся перечисления;)
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 ) );
}
Если вам нужно добавить только одно значение на карту, вы можете использовать Collections.singletonMap:
Map<K, V> map = Collections.singletonMap(key, value)
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.
Я прочитал ответы и решил написать свой собственный конструктор карт. Не стесняйтесь копировать-вставить и наслаждаться.
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
, что сделало его более подходящим для статического импорта.
Примечание. Этот ответ на самом деле относится к вопросу Как напрямую инициализировать 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}
Даже с хорошим классом 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 для источника, документации и примеров.
Вот код от 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.
Здесь есть несколько хороших ответов, но я хочу предложить еще один.
Создайте свой собственный статический метод для создания и инициализации 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);
В Java 8 процедурный подход также может быть заключен в Supplier
:
Map<String,String> m = ((Supplier<Map<String,String>>)(() -> {
Map<String,String> result = new HashMap<>();
result.put("foo","hoo");
...
return result;
)).get();
Это только гипотетический способ, но может пригодиться, если вам действительно нужен один-лайнер.
Мне нравится использовать метод статического инициализатора, когда у меня есть конкретная реализация абстрактного класса, который определил конструктор инициализации, но не конструктор по умолчанию, но я хочу, чтобы мой подкласс имел конструктор по умолчанию.
Например:
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...
}
}
Теперь, когда 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 Было бы любопытно мнение других об этом (это мягко зло...)
Если вы можете использовать строковое представление ваших данных, это тоже вариант в 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]));
Я сделал что-то совсем другое. Не самое лучшее, но это работает для меня. Возможно, он может быть "обобщен".
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;
}
Map<Object, Object>
(хотя можно использовать аналогичный подход с String[][]
для Map<String,String>
и аналогичным для других Map<T,T>
. Это не работает для карт, где тип ключа отличается от типа значения.
Мне нравится синтаксис анонимного класса; это просто меньше кода. Тем не менее, один из основных я обнаружил, что вы не сможете сериализовать этот объект путем удаленного доступа. Вы получите исключение из-за невозможности найти анонимный класс на удаленной стороне.
Второй метод может при необходимости использовать защищенные методы. Это может быть полезно для инициализации классов, неизменяемых после построения.