题 如何初始化静态地图?


你会如何在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");
        }
    };
}

931
2018-02-03 15:41


起源


要在Java 8中初始化地图: stackoverflow.com/a/37384773/1216775 - i_am_zero
请永远不要使用 双括号初始化  - 这是一个黑客,一个泄漏内存和导致其他问题的简单方法。 - dimo414


答案:


在这种情况下,实例初始化器只是语法糖,对吧?我不明白为什么你需要一个额外的匿名类来初始化。如果正在创建的类是最终的,它将无法工作。

您也可以使用静态初始化器创建不可变映射:

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

946
2017-08-31 13:58



这是我多年来使用的成语,我从来没有人盯着它。我对不可修改的常量集和列表也这样做。 - jasonmp85
如何使用String键处理HashMap <String,String>。 Map对象不允许我有一个String键,所以我不能使用unmodifiableMap()。我想投射到HashMap也会破坏目的。有任何想法吗? - Luke
@Luke我严重怀疑Android有这样的局限性。这根本没有意义。找到快速搜索 这个问题 这里(和许多其他人)似乎暗示你可以在Android中使用String键作为Map对象。 - mluisbrown
所以没有其他人不愿意调查,我可以确认在Android上使用String键对于Map对象没有问题。 - Jordan
乔丹:现在这是一个古老的话题,但我怀疑@Luke正在尝试使用字符串作为具有不同密钥类型的地图中的密钥,例如Map <Integer,String>。 - Miserable Variable


我喜欢 番石榴 初始化静态不可变映射的方法:

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项目中使用这个库,我 非常 建议尝试一下! Guava已经迅速成为Java最受欢迎和最有用的免费第三方库之一 SO用户同意。 (如果您是新手,那么该链接背后有一些优秀的学习资源。)


更新(2015年):至于 Java 8好吧,我仍然会使用番石榴方法,因为它比其他任何方式更清洁。如果你不想要番石榴依赖,考虑一下 普通的旧init方法。黑客入侵 二维数组和Stream API 如果你问我,那就太难看了,如果你需要创建一个键和值不是同一类型的Map,就会变得更加丑陋(比如 Map<Integer, String> 在问题中)。

至于番石榴的未来,关于Java 8,Louis Wasserman 这样说 早在2014年,[更新] 2016年宣布 Guava 21将需要并正确支持Java 8


更新(2016):作为 Tagir Valeev指出Java 9 除了纯JDK之外,最后还是要使用纯粹的JDK 便利工厂的方法 对于收藏:

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

370
2018-02-03 21:40



好像我们的SO管理员已经删除了我链接的令人尊敬的“最有用的免费第三方Java库”问题。 :(该死的。 - Jonik
我同意,这是初始化常量贴图的最好方法。不仅更具可读性,而且从那时起 Collections.unmodifiableMap 返回基础地图的只读视图(仍可以修改)。 - crunchdog
我现在可以看到删除的问题(10k + rep),所以这是一个 “最实用的免费第三方Java库”副本。它只是第一页,但至少你可以找到 番石榴资源 上文提到的。 - Jonik
我真的更喜欢这种方法,虽然知道如何在没有额外依赖性的情况下做到这一点是有益的。 - Wrench
JEP 186仍未关闭,因此它可能会引入与集合文字相关的新功能 - cybersoft


我会用:

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);
    }
}
  1. 它避免了匿名课程,我个人认为这是一种不好的风格,并避免
  2. 它使地图的创建更加明确
  3. 它使地图不可修改
  4. 因为MY_MAP是不变的,我会把它命名为常数

160
2017-07-15 21:29



在纯JDK选项(没有库)中,我最喜欢这个,因为地图定义明显与其初始化相关联。也同意不断命名。 - Jonik


Java 5提供了这种更紧凑的语法:

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

157
2018-02-03 15:44



该技术称为双括号初始化: stackoverflow.com/questions/1372113/... 它不是一种特殊的Java 5语法,它只是一个带有实例初始化程序的匿名类的技巧。 - Jesper
关于双括号初始化的快速问题:执行此操作时,Eclipse会发出有关缺少序列ID的警告。一方面,我不明白为什么在这种特定情况下需要一个序列号ID,但另一方面,我通常不喜欢抑制警告。你对此有何看法? - nbarraille
@nbarraille那是因为 HashMap implements Serializable。由于您实际使用此“技巧”创建了HashMap的子类,因此您隐式创建了Serializable类。为此你应该提供一个serialUID。 - noone
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 - Mark Jeronimus
@MarkJeronimus - 建议使用 是 静态上下文。性能可能更差,但在处理可能少量静态定义的地图时并不明显。 HashMap.equals 定义于 AbstractMap 并继续努力 任何 Map的子类,所以这里不关心。钻石操作员的事情很烦人,但正如所提到的那样现在已经解决了。 - Jules


第二种方法的一个优点是你可以用它包装 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!

80
2017-09-14 00:44



你能不能通过将new运算符移动到static {}块并包装它来在第一种方法中轻松完成此操作? - Patrick
无论如何,我会将构造函数调用移动到静态初始化中。别的什么看起来很奇怪。 - Tom Hawtin - tackline
任何想法使用匿名类而不是具体类可能会有什么性能? - Kip


这是一个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


52
2017-12-29 10:07



我冒昧地添加了一个与问题和其他答案等效的版本:init一个其键和值不同类型的Map(所以 String[][] 不会这样做, Object[][] 需要)。恕我直言,这种做法很丑陋(对阵演员来说更是如此)而且很难记住;不会自己用它。 - Jonik
要在Java 8中初始化地图: stackoverflow.com/a/37384773/1216775 - i_am_zero


在Java 9中:

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

看到 JEP 269 详情。 JDK 9到达了 一般可用性 在2017年9月。


39
2017-12-18 23:10



或者,如果您想要超过10个键值对,则可以使用 Map.ofEntries - ZhekaKozlov
这一切都很干净,直到你意识到 它是如何实施的 - Midnightas
呃这太可悲了 - 看起来它只支持10个条目,之后你需要使用条目。瘸。 - Somaiah Kumbera


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 Collections静态初始化原始映射。

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 Collections的提交者


27
2018-02-03 15:55





在这种情况下,我永远不会创建一个匿名子类。静态初始化程序同样运行良好,如果您想使地图不可修改,例如:

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

24
2018-02-03 21:33



在哪种情况下你会使用匿名子类来初始化一个hashmap呢? - dogbane
永远不要初始化集合。 - eljenso
你能解释为什么使用静态初始化器比创建匿名子类更好吗? - leba-lev
@rookie在其他有利于静态init的答案中给出了几个原因。这里的目标 是 初始化,所以为什么要引入子类,除了可以保存一些键击? (如果你想节省键击,那么Java绝对不是一种编程语言的好选择。)我在Java编程时使用的一条经验法则是:子类尽可能少(从而无法合理避免)。 - eljenso
@eljenso - 我一般支持子类语法的原因是它 将初始化内联,它所属的位置。第二个最佳选择是调用返回初始化映射的静态方法。但我担心我会查看你的代码,并且必须花几秒钟来确定MY_MAP来自何处,而那时我不想浪费。对可读性的任何改进都是一个奖励,性能影响很小,所以它似乎是我的最佳选择。 - Jules


也许结账很有意思 Google Collections,例如他们在他们的页面上的视频。它们提供了各种方法来初始化地图和集合,并提供不可变的集合。

更新:此库现已命名 番石榴


14
2017-09-19 08:48





我喜欢匿名类,因为它很容易处理它:

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

14
2018-05-23 07:17