Як я можу ініціалізувати статичну карту?


1131

Як би ви ініціалізували статику Mapна Java?

Перший спосіб: статичний ініціалізатор Другий
спосіб: ініціалізатор екземпляра (анонімний підклас) або якийсь інший метод?

Які плюси і мінуси кожного?

Ось приклад, що ілюструє два способи:

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

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

    private static final Map<Integer, String> myMap2 = new HashMap<>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}

2
Для ініціалізації карти в Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal

2
Будь ласка, ніколи не використовуйте подвійну ініціалізацію дужок - це злом і простий спосіб просочити пам'ять і викликати інші проблеми.
dimo414

Java 9? Якщо записи лічильник <= 10 використання Map.ofще Map.ofEntries, перевірте stackoverflow.com/a/37384773/1216775
akhil_mittal

Відповіді:


1106

У цьому випадку ініціалізатор екземплярів - це лише синтаксичний цукор, правда? Я не бачу, для чого вам потрібен додатковий анонімний клас просто для ініціалізації. І це не спрацює, якщо створений клас остаточний.

Ви можете створити непорушну карту, використовуючи також статичний ініціалізатор:

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

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

3
Як би я обробляв HashMap <String, String> за допомогою клавіші String. Об'єкт Map не дозволяє мені мати рядок String, тому я не можу використовувати unmodifiableMap (). Я думаю, що кастинг на HashMap також переможе мету. Будь-які ідеї?
Лука

30
@Luke я серйозно сумніваюся, що Android має таке обмеження. Це взагалі не має сенсу. Швидкий пошук знайшов це питання тут (і багато інших), яке, мабуть, означає, що ви можете використовувати клавішу String для об’єкта Map в Android.
mluisbrown

11
Тому більше ніхто не намагається розслідувати, я можу підтвердити, що немає проблеми з використанням клавіші String для об’єкта Map на Android.
Йордан

11
Йорданія: це вже давня тема, але я підозрюю, що @Luke намагався використовувати рядок як ключ у карті, який мав інший тип ключів, наприклад, Map <Integer, String>.
Жалюгідна змінна

445

Мені подобається спосіб Guava ініціалізації статичної, непорушної карти:

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

Як бачите, це дуже стисло (через зручні фабричні методи 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 Collections . Якщо ви ще не використовуєте цю бібліотеку у своєму проекті Java, настійно рекомендую спробувати її! Гуава швидко став однією з найпопулярніших і корисних безкоштовних сторонніх ліфтів для Java, як погоджуються і його колеги . (Якщо ви новачок у ньому, за цим посиланням є кілька чудових навчальних ресурсів.)


Оновлення (2015) : Що стосується Java 8 , ну, я б все-таки використовував підхід Guava, оскільки він чистіший за все інше. Якщо ви не хочете залежності Гуави, розгляньте звичайний старий метод init . Хак з двовимірним масивом та API Stream є досить неприємним, якщо ви запитаєте мене, і стає більш потворним, якщо вам потрібно створити карту, чиї ключі та значення не є однотипними (як Map<Integer, String>у запитанні).

Що стосується майбутнього Guava взагалі, що стосується Java 8, Луї Вассерман сказав про це ще в 2014 році, а [ оновлення ] в 2016 році було оголошено, що Guava 21 вимагатиме і належну підтримку Java 8 .


Оновлення (2016 р.) : Як зазначає Тагір Валєєв , Java 9 нарешті зробить це чистим, не використовуючи нічого, крім чистого JDK, додавши зручні заводські методи для колекцій:

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

21
Схоже, наші колеги-адміністратори ТА видалили поважне питання "Найкорисніші безкоштовні сторонні бібліотеки Java", з яким я пов’язаний. :( Прокляття їх.
Jonik

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

11
Зараз я бачу видалені запитання (з 10k + повтором), тому ось копія "Найкорисніших безкоштовних сторонніх бібліотек Java" . Це лише перша сторінка, але принаймні ви можете знайти згадані вище ресурси Guava .
Джонік

2
Я дуже віддаю перевагу такому підходу, хоча корисно знати, як це зробити без зайвих залежностей.
Гайковий ключ

2
JEP 186 все ще не закритий, тому він може представити нові функції, пов’язані з колекційними літералами
cybersoft

182

Я б використав:

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

    private static Map<Integer, String> createMap() {
        Map<Integer, String> result = new HashMap<>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  1. це уникає анонімного класу, який я особисто вважаю поганим стилем, і уникаю
  2. це робить створення карти більш чітким
  3. це робить карту незмінною
  4. оскільки MY_MAP є постійним, я б назвав це як константа

3
З чистих варіантів JDK (без libs) мені це найбільше подобається, оскільки визначення карти чітко пов'язане з її ініціалізацією. Також домовились про постійне називання.
Jonik

Мені ніколи не спадало на думку, що ти можеш це зробити.
romulusnr

181

Java 5 надає цей більш компактний синтаксис:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};

46
Ця методика називається подвійною ініціалізацією дужок : stackoverflow.com/questions/1372113/… Це не особливий синтаксис Java 5, це лише фокус з анонімним класом з ініціалізатором екземпляра.
Jesper

13
Швидке запитання щодо ініціалізації подвійної дужки: При цьому Eclipse видає попередження про відсутній серійний ідентифікатор. З одного боку, я не бачу, для чого потрібен серійний ідентифікатор у цьому конкретному випадку, але з іншого боку, як правило, не люблю подавляючих попереджень. Які ваші думки з цього приводу?
nbarraille

8
@nbarraille Це тому HashMap implements Serializable. Оскільки ви фактично створюєте підклас HashMap, використовуючи цей "трюк", ви неявно створюєте клас Serializable. І для цього вам слід надати serialUID.
ніхто

5
Double brace initialization can cause memory leaks when used from a non-static context, because the anonymous class created will maintain a reference to the surrounding object. It has worse performance than regular initialization because of the additional class loading required. It can cause equals() comparisons to fail, if the equals() method does not accept subclasses as parameter. And finally, pre Java 9 it cannot be combined with the diamond operator, because that cannot be used with anonymous classes.- IntelliJ
Марк Єронімус

3
@MarkJeronimus - Пропоноване використання є статичним контекстом. Ефективність може бути гіршою, але не помітно, якщо це стосується імовірно невеликої кількості статично визначених карт. HashMap.equalsвизначено в AbstractMapі працює на будь-якому підкласі Map, тому це не хвилює. Дія алмазного оператора дратує, але, як було сказано, зараз вирішено.
Жуль

95

Однією з переваг другого методу є те, що ви можете його обернути, 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
Ви не можете легко зробити це в першому методі, перемістивши нового оператора в статичний {} блок і загорнувши його?
Патрік

2
Я все-таки переміщу виклик конструктора в статичний ініціалізований. Все інше просто дивно виглядає.
Том Хотін - тайклін

2
будь-яка ідея, яку ефективність може бути від використання анонімного класу на відміну від конкретного класу?
Кіп

62

Ось однолінійний ініціалізатор статичної карти 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/a/37384773/3950982


7
Я взяв на себе сміливість додати версію, що еквівалентна питанню та іншим відповідям: надішліть карту, ключі та значення якої різного типу (так що String[][]не робити, Object[][]не потрібно). ІМХО, такий підхід некрасивий (тим більше з кастами) і важко запам'ятати; Я б не використовував його сам.
Джонік

57

Map.of на Java 9+

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

Детальніше див. У JEP 269 . JDK 9 досяг загальної готовності у вересні 2017 року.


7
Або якщо ви хочете більше 10 пар ключових значень, можете скористатисяMap.ofEntries
Жека Козлов

8
Це чисто і все, поки ви не зрозумієте, як це було здійснено
середина

Фу, це так сумно - схоже, що він підтримує лише 10 записів, після яких вам потрібно використовувати Entries. Кульгав.
Сомая Кумбера

2
Чистота впровадження в JDK не повинна мати значення, поки вона працює і відповідає договору. Як і будь-яка чорна скринька, деталі впровадження завжди можна виправити у майбутньому, якщо насправді знадобиться ...
vikingsteve

@mid Це єдиний безпечний спосіб зробити це на Java.
Люк Хатчісон,

44

Java 9

Ми можемо використовувати Map.ofEntries, закликаючи Map.entry( k , v )створити кожен запис.

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

2
new SimpleEntry<>()Шлях набагато менш читабельним , ніж статичні put(): /
Danon

32

З колекціями Eclipse працює все наступне:

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 Collection.

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


1
Я дуже хочу, щоб колекції Eclipse були бібліотекою колекцій за замовчуванням для Java. Мені це подобається набагато більше, ніж Guava + JCL.
Кенні

29

Я б ніколи не створював анонімний підклас у цій ситуації. Статичні ініціалізатори працюють однаково добре, якщо ви хочете зробити карту незмінною, наприклад:

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
У якій ситуації ви б використали анонімний підклас, щоб потім ініціалізувати хешмап?
собачка

6
Ніколи не ініціалізувати колекцію.
eljenso

Чи можете ви пояснити, чому використання статичного ініціалізатора є кращим вибором, ніж створення анонімного підкласу?
leba-lev

3
@rookie Існує кілька причин, наведених в інших відповідях на користь статичного init. Мета тут - ініціалізація, тож навіщо вводити цей підклас, окрім можливо, щоб зберегти кілька натискань клавіш? (Якщо ви хочете заощадити на натисканнях клавіш, Java, безумовно, не є вдалим вибором як мови програмування.) Одне правило, яке я використовую при програмуванні на Java, - це: підклас якомога менше (і ніколи, коли цього можна обґрунтовано уникнути).
eljenso

@eljenso - причина, якою я загалом віддаю перевагу синтаксису підкласу, полягає в тому, що він ставить ініціалізацію в рядок, куди належить . Другий найкращий вибір - викликати статичний метод, який повертає ініціалізовану карту. Але я боюся, що я перегляну твій код і мені доведеться витратити кілька секунд на відпрацювання, звідки походить MY_MAP, і саме цей час я не хочу витрачати. Будь-яке поліпшення читабельності - це бонус, а наслідки для продуктивності мінімальні, тому мені здається найкращим варіантом.
Жуль

18

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

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


17

Мені подобається анонімний клас, тому що з ним легко впоратися:

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

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

І пропонується використовувати незмінювальну карту для інших констант, але це не може трактуватися як постійне.


10

Я можу настійно запропонувати стиль "подвійної ініціалізації дужок" над статичним стилем блоку.

Хтось може прокоментувати, що їм не подобається анонімний клас, накладні витрати, продуктивність тощо.

Але те, що я більше враховую, - це читаність коду та ремонтопридатність. У цій точці зору, я вважаю, що подвійний брекет - кращий стиль коду, а не статичний метод.

  1. Елементи вкладені та вбудовані.
  2. Це більше ОО, а не процедурне.
  3. вплив на продуктивність дійсно невеликий і його можна ігнорувати.
  4. Краща підтримка контуру IDE (а не багато анонімних статичних блоків {})
  5. Ви зберегли кілька рядків коментарів, щоб налагодити їх стосунки.
  6. Попередити можливі витоки / екземпляри елементів неініціалізованого об'єкта за винятком та оптимізатором байт-коду.
  7. Не турбуйтеся про порядок виконання статичного блоку.

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

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


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

Я включаю Apache Commons у всі версії, тому, при нещасній відсутності методу Arrays.asMap( ... )на звичайній Яві, я думаю, що це найкраще рішення. Винаходити колесо, як правило, нерозумно. Дуже незначним недоліком є ​​те, що для дженериків воно потребуватиме безперебійного перетворення.
мійський гризун

Версія @mikerodent 4.1 є загальною: загальна статична <K, V> Карта <K, V> putAll (остаточна карта <K, V> карта, кінцевий масив [] масиву)
agad

Tx ... так, я використовую 4.1, але мені все одно до SuppressWarnings( unchecked )Eclipse з такою лінією, якMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
Майк гризун

@mikerodent чи не через Object [] [] ? Дивіться оновлену білизну - я не маю жодного попередження в Eclipse.
agad

Як дивно ... навіть коли я йду, String[][]отримую "попередження"! І звичайно, це працює лише в тому випадку, якщо ваш Kі Vє одного класу. Я вважаю, що ви не встановили (зрозуміло) "неперевірену конверсію" на "Ігнорувати" у вашій програмі Eclipse?
мійський гризун

7

Ось мій улюблений, коли я не хочу (або не можу) використовувати 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));

7

Якщо ви хочете незмінну карту, нарешті Java 9 додав метод прохолодних заводский ofдо Mapінтерфейсу. Аналогічний метод додається також до Set, List.

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


6

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

Примітка . Питання нічого не говорить про те, щоб зробити карту незмінною, тому я не залишаю цього, але знайте, що це можна легко зробити 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 для одного). Ви отримаєте дуже дивні помилки.

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

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


3
У цьому конкретному випадку він є статичним, тому жодного зовнішнього екземпляра.
Том Хотін - тайклін

Можливо, XStream не повинен намагатися серіалізувати такі речі (це статично. Навіщо вам потрібно серіалізувати статичну змінну?)
jasonmp85

5

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

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

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
  • він безпечний і легко вміщує різні типи за ключовими і цінними показниками.

при необхідності можна використовувати toMapпідпис, включаючи постачальника карт, щоб вказати тип карти.
zrvan



4

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

Ще один простий спосіб ініціалізації статичної карти за допомогою цієї функції утиліти:

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

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

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

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)

(ви можете використовувати "імпорт статики", щоб уникнути необхідності префіксувати ім'я методу)

Мені було корисно надати подібні статичні методи для інших колекцій (список, набір, sortedSet, sortedMap тощо)

Це не так приємно, як ініціалізація об'єктів json, але це крок у тому напрямку, що стосується читабельності.


3

Оскільки Java не підтримує літерали карт, екземпляри карт завжди повинні бути чітко створені та заповнені.

На щастя, можна наблизити поведінку картографічних літералів на 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}

Це набагато зручніше, ніж створювати та заповнювати на карті елемент за один раз.


2

JEP 269 пропонує деякі зручні фабричні методи для API колекцій. Даних заводських методів немає в поточній версії Java, яка становить 8, але планується для випуску Java 9.

Бо Mapіснують два заводські методи: 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 .


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

Я прочитав відповіді, і вирішив написати власного конструктора карт. Не соромтеся копіювати та вставляти.

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замість цього перейменував його , зробивши його більш придатним для статичного імпорту.

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