如何直接以字面量方式初始化HashMap?

1597

有没有一种像这样初始化Java HashMap的方法?:

Map<String,String> test = 
    new HashMap<String, String>{"test":"test","test":"test"};

什么是正确的语法?我没有找到任何相关的内容。这是可能的吗?我正在寻找一种将一些“最终/静态”值放入地图中的最短/最快方法,这些值在创建地图时预先知道并且永远不会改变。


1
请参考以下链接:https://dev59.com/1XI-5IYBdhLWcg3wCztn#2041789 - Bozho
密切相关:https://dev59.com/1nRB5IYBdhLWcg3wyqOo(这两个问题都是关于使用静态、最终值初始化常量映射。) - Jonik
http://stackoverflow.com/questions/36951414/initialize-a-map-hashmap-in-java/36951468#36951468 - Ramkumar Pillai
在Java 9中:http://www.techiedelight.com/initialize-map-java9/ - Kamil Tomasz Jarmusik
4
这并不是 链接问题 的重复。那个问题是关于 static 的,而这个问题是问如何通过字面量语法进行实例化。请投票重新开放。也许这个问题与其他问题重复了;如果是这样,请重新开放并通过链接到一个真正源头的问题再次关闭它。 - Basil Bourque
显示剩余3条评论
22个回答

2101

所有版本

如果您只需要一个条目:可以使用Collections.singletonMap("key", "value")

适用于Java版本9或更高版本:

是的,现在这是可能的。在Java 9中添加了一些工厂方法来简化创建映射的过程:

// this works for up to 10 elements:
Map<String, String> test1 = Map.of(
    "a", "b",
    "c", "d"
);

// this works for any number of elements:
import static java.util.Map.entry;    
Map<String, String> test2 = Map.ofEntries(
    entry("a", "b"),
    entry("c", "d")
);
在上述示例中,testtest2都将是相同的,只是以不同的方式表达Map。 Map.of方法定义了最多十个元素的映射,而Map.ofEntries方法没有这样的限制。
请注意,在此情况下,生成的映射将是不可变的映射。如果您想使映射可变,则可以再次复制它,例如使用mutableMap = new HashMap<>(Map.of("a", "b"));。还要注意,此情况下键和值必须不为null
(另请参见 JEP 269 Javadoc
对于Java版本8及以下:
不,您将不得不手动添加所有元素。您可以在匿名子类的初始化程序中使用来缩短语法。
Map<String, String> myMap = new HashMap<String, String>() {{
    put("a", "b");
    put("c", "d");
}};

然而,匿名子类可能会在某些情况下引入不需要的行为。例如:

  • 它会生成一个额外的类,增加内存消耗、磁盘空间消耗和启动时间
  • 如果是非静态方法:它会持有对创建方法所调用对象的引用。这意味着,在仍然引用创建的 map 对象时,外部类的对象无法被垃圾回收,从而阻止了额外的内存释放

使用函数进行初始化也可以让你在初始化器中生成一个 map,但避免了不良副作用:

Map<String, String> myMap = createMap();

private static Map<String, String> createMap() {
    Map<String,String> myMap = new HashMap<String,String>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    return myMap;
}

3
如果你想在函数中初始化元素,那么这种方法行不通... - Michael
12
@Michael:是的,如果你想使用一个函数,那么你就不能使用不是函数的东西。但是你为什么要这样做呢? - yankee
9
如果您需要一个只有一个条目的 Map,可以使用 Collections.singletonMap() 方法 :) - skwisgaar
3
现在稳定版Java 9已经发布,我更喜欢使用这个Javadoc链接(https://docs.oracle.com/javase/9/docs/api/java/util/Map.html)。+1因为少了一个依赖项! - Franklin Yu
3
应该使用Map.entry而不是entry,但其他方面都可以工作。谢谢! - Cameron Hudson
显示剩余13条评论

1158

这是一种方式。

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

然而,你应该小心并确保你理解上面的代码(它创建了一个继承自HashMap的新类)。因此,你应该在这里阅读更多: http://www.c2.com/cgi/wiki?DoubleBraceInitialization 或者简单地使用Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);

ImmutableMap.of 最多适用于5个条目。否则,请使用构建器:源代码


90
它能够发挥作用,但是有些不美观且有看不见的副作用,用户在执行之前应该了解这些副作用——例如,在现场生成一个完整的匿名类。 - jprete
109
没错,这就是我写关于小心的原因并附上链接描述的原因。 - gregory561
6
好的链接。链接中提到的 GreencoddsTenthRuleOfProgramming 值得一读。 - michaelok
21
你能否把 "of" 方法的限制改成最多可以接受 5 对键值对,或者添加以下内容作为替代方案:"ImmutableMap.builder.put("k1","v1").put("k2","v2").build()" ? - kommradHomer
3
为Guava http://google.github.io/guava/releases/18.0/api/docs/com/google/common/collect/ImmutableMap.html:该页面是Guava库中不可变映射(键值对)的API文档,提供了使用和创建不可变映射的方法。 - Dan Jay
显示剩余9条评论

369

如果您允许第三方库,您可以使用GuavaImmutableMap来实现类似字面的简洁:

Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");

这适用于最多5个键/值对,否则您可以使用它的构建器

Map<String, String> test = ImmutableMap.<String, String>builder()
    .put("k1", "v1")
    .put("k2", "v2")
    ...
    .build();


  • 注意:Guava的ImmutableMap实现与Java的HashMap实现不同(最显著的是它是不可变的,不允许空键/值)
  • 更多信息,请参阅Guava的用户指南文章,了解其不可变集合类型

29
另外,番石榴具有ImmutableMap.builder.put("k1","v1").put("k2","v2").build();的功能。 - Xetius
21
ImmutableMap与HashMap不同,因为ImmutableMap在处理空值时会失败,而HashMap则不会。 - Gewthen
3
只是为了帮助其他可能遇到这个问题的人。 您必须键入构建器以使其成为Map<String,String>,如下所示:Map<String,String> test = ImmutableMap.<String,String>builder().put("k1", "v1").put("k2", "v2").build(); - Thiago
这太棒了,Jens! - Gaurav

121

目前(2021年)还没有直接的方法可以做到这一点 - Java没有Map字面量(尽管我认为它们曾计划在Java 8中加入,但并未实现)。

有些人喜欢这样做:

Map<String,String> test = new HashMap<String, String>(){{
       put("test","test"); put("test","test");}};

这将创建一个HashMap的匿名子类,它的实例初始化程序会将这些值放入其中。(顺便说一句,映射表不能包含两次相同的值,你的第二个put会覆盖第一个。我将在下一个示例中使用不同的值。)

对于局部变量,通常方式是这样的:

Map<String,String> test = new HashMap<String, String>();
test.put("test","test");
test.put("test1","test2");

如果您的test映射是一个实例变量,请将初始化放在构造函数或实例初始化程序中:

Map<String,String> test = new HashMap<String, String>();
{
    test.put("test","test");
    test.put("test1","test2");
}

如果你的test映射是一个类变量,请将初始化放在静态初始化程序中:

static Map<String,String> test = new HashMap<String, String>();
static {
    test.put("test","test");
    test.put("test1","test2");
}

如果您希望您的地图永远不会更改,那么您应该在初始化后通过Collections.unmodifiableMap(...)来包装您的地图。您也可以在静态初始化程序中执行此操作:

static Map<String,String> test;
{
    Map<String,String> temp = new HashMap<String, String>();
    temp.put("test","test");
    temp.put("test1","test2");
    test = Collections.unmodifiableMap(temp);
}

我不确定你是否可以将test设为final... 试一下并在此报告。

自Java 9以来,你还可以使用Map.of(...)和Map.ofEntries()语法,如 yankee 的答案中所解释的。


65
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};

简单明了。我认为这个答案加上扩展评论部分会是最好的答案。 - ooolala
17
需要注意的是,这会涉及到内存方面的影响。 - Alkanshel
1
@Amalgovinus 基本上,通过创建一个新的子类,你正在将类型参数从HashMap硬编码到这个子类中。只有在实际提供它们时才能起作用。(对于一个新的(空)HashMap,类型参数是无关紧要的。) - Paŭlo Ebermann
2
我喜欢它的简洁性,但它会创建不必要的匿名类,并存在此处描述的问题:http://www.c2.com/cgi/wiki?DoubleBraceInitialization - udachny
1
@hello_its_me:因为它与https://dev59.com/2mw15IYBdhLWcg3wJ4UR#6802512答案相同,只是格式不同。而且在这种情况下,相较于紧凑格式,扩展格式并没有提供额外的可读性价值。 - Daniel Hári
显示剩余2条评论

46

另一种方法是使用普通的Java 7类和可变参数:创建一个名为HashMapBuilder的类,并添加以下方法:

public static HashMap<String, String> build(String... data){
    HashMap<String, String> result = new HashMap<String, String>();

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

    String key = null;
    Integer step = -1;

    for(String value : data){
        step++;
        switch(step % 2){
        case 0: 
            if(value == null)
                throw new IllegalArgumentException("Null key value"); 
            key = value;
            continue;
        case 1:             
            result.put(key, value);
            break;
        }
    }

    return result;
}

使用方法如下:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");

我写了一个受你启发的答案:https://dev59.com/1nRB5IYBdhLWcg3wyqOo#52266947 - Gerold Broser
2
另一种使用Apache Utils的解决方案从未被提及,但是在先前的Java版本中可读性更好: MapUtils.putAll(new HashMap<String, String>(), new Object[] { "My key", "my value", ... - Rolintocour
一个优美而易于理解的例子! 此外,Apache Utils也非常棒。 - Tihamer

20
使用Java 9及更高版本中的Map.of...方法。
Map< String , String > animalSounds =
    Map.of(
        "dog"  , "bark" ,   // key , value
        "cat"  , "meow" ,   // key , value
        "bird" , "chirp"    // key , value
    )
;

Map.of

Java 9新增了一系列的Map.of静态方法,可以满足你的需求:使用字面语法实例化一个不可变的Map
这个映射(一组条目)是不可变的,所以在实例化后不能添加或删除条目。此外,每个条目的键和值都是不可变的,不能更改。请参阅Javadoc了解其他规则,例如不允许NULL值,不允许重复键,并且映射的迭代顺序是任意的。
让我们来看看这些方法,使用一些示例数据来创建一个星期几到工作人员的映射。
Person alice = new Person( "Alice" );
Person bob = new Person( "Bob" );
Person carol = new Person( "Carol" );

Map.of()

Map.of 创建一个空的 Map。不可修改,因此无法添加条目。以下是一个示例,这是一个没有任何条目的空映射。

Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();

dailyWorkerEmpty.toString(): {}
Map.of( … )
Map.of( k , v , k , v , …) 是几个方法,可以接受 1 到 10 对键值对。这里是两个条目的示例。
Map < DayOfWeek, Person > weekendWorker = 
        Map.of( 
            DayOfWeek.SATURDAY , alice ,     // key , value
            DayOfWeek.SUNDAY , bob           // key , value
        )
;

周末工作者.toString():{星期日=人物{name='Bob' },星期六=人物{name='Alice'}}。

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )接受任意数量实现Map.Entry接口的对象。Java提供了两个实现该接口的类,一个是可变的,另一个是不可变的:AbstractMap.SimpleEntryAbstractMap.SimpleImmutableEntry。但我们不需要指定具体的类。我们只需要调用Map.entry( k , v )方法,传入我们的键和值,然后我们会得到一个实现Map.Entry接口的某个类的对象。

Map < DayOfWeek, Person > weekdayWorker = Map.ofEntries(
        Map.entry( DayOfWeek.MONDAY , alice ) ,            // Call to `Map.entry` method returns an object implementing `Map.Entry`. 
        Map.entry( DayOfWeek.TUESDAY , bob ) ,
        Map.entry( DayOfWeek.WEDNESDAY , bob ) ,
        Map.entry( DayOfWeek.THURSDAY , carol ) ,
        Map.entry( DayOfWeek.FRIDAY , carol )
);

weekdayWorker.toString(): {WEDNESDAY=Person{ name='Bob' }, TUESDAY=Person{ name='Bob' }, THURSDAY=Person{ name='Carol' }, FRIDAY=Person{ name='Carol' }, MONDAY=Person{ name='Alice' }}

Map.copyOf

Java 10新增了方法Map.copyOf。传入一个现有的map,返回该map的不可变副本。
为了提高效率,如果传入的map已经是真正的不可变对象,copyOf方法将返回对原始对象的引用,而不会创建新的map。
关于Collections.unmodifiableMap 提示:如果需要/期望一个真正的不可变map,请优先使用Map.copyOf而不是Collections.unmodifiableMapCollections方法生成的是对原始map的视图,只是一个简单的包装器,而不是真正的副本。好处是节省内存,但缺点是对原始map的修改会反映出来。
如果原始地图被修改(put、remove等),本应不可修改的部分实际上会显示修改。相比之下,如果传递的地图尚未真正不可变,则Map.copyOf确实会生成传递地图的真正副本。
record Person( String name ) { }
Person alice = new Person ( "Alice" );
Person bob = new Person ( "Bob" );
Map < DayOfWeek, Person > weekendWorkerMutable = HashMap.newHashMap ( 2 );
weekendWorkerMutable.put ( DayOfWeek.SATURDAY , bob );
weekendWorkerMutable.put ( DayOfWeek.SUNDAY , bob );
System.out.println ( "weekendWorkerMutable = " + weekendWorkerMutable );

Map < DayOfWeek, Person > weekendWorkerSupposedlyUnmodifiable = Collections.unmodifiableMap ( weekendWorkerMutable );
System.out.println ( "weekendWorkerSupposedlyUnmodifiable = " + weekendWorkerSupposedlyUnmodifiable );
Map < DayOfWeek, Person > trueCopy = Map.copyOf ( weekendWorkerSupposedlyUnmodifiable );
System.out.println ( "trueCopy = " + trueCopy );

weekendWorkerMutable.put ( DayOfWeek.SATURDAY , alice );  // <--- Modify the original.

System.out.println ( " ----- After mutating the original mutable map  ----- " );
System.out.println ( "weekendWorkerSupposedlyUnmodifiable = " + weekendWorkerSupposedlyUnmodifiable );
System.out.println ( "trueCopy = " + trueCopy );

当运行时:
我们可以看到,原本被认为是不可修改的实际上是可以通过间接方式进行修改的,即通过修改原始地图来修改不可修改地图。
相比之下,`copyOf` 方法生成了一个真正的副本,在将 Alice 分配给 Bob 后,Bob 仍然被显示为整个周末都在工作。
weekendWorkerMutable = {SATURDAY=Person[name=Bob], SUNDAY=Person[name=Bob]}
weekendWorkerSupposedlyUnmodifiable = {SATURDAY=Person[name=Bob], SUNDAY=Person[name=Bob]}
trueCopy = {SUNDAY=Person[name=Bob], SATURDAY=Person[name=Bob]}
 ----- After mutating the original mutable map  ----- 
weekendWorkerSupposedlyUnmodifiable = {SATURDAY=Person[name=Alice], SUNDAY=Person[name=Bob]}
trueCopy = {SUNDAY=Person[name=Bob], SATURDAY=Person[name=Bob]}

注意事项

请注意,通过Map.of创建的映射的迭代顺序是不确定的。条目的顺序是任意的。不要根据所见的顺序编写代码,因为文档警告顺序可能会发生变化。

请注意,所有这些Map.of...方法返回一个未指定类别的Map。底层具体类甚至可能在Java的不同版本之间有所变化。这种匿名性使得Java可以从各种实现中选择最适合您特定数据的实现。例如,如果您的键来自枚举,Java可能在内部使用EnumMap


15

JAVA 8

在纯粹的Java 8中,您还可以使用Streams/Collectors来完成这项工作。

Map<String, String> myMap = Stream.of(
         new SimpleEntry<>("key1", "value1"),
         new SimpleEntry<>("key2", "value2"),
         new SimpleEntry<>("key3", "value3"))
        .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));

这种方法的优点是不会创建匿名类。

请注意这些导入:

import static java.util.stream.Collectors.toMap;
import java.util.AbstractMap.SimpleEntry;

当然,正如其他答案中提到的那样,在 Java 9 及其以上版本中,您有更简单的方法来做同样的事情。


1
如果您正在使用Apache Commons,您也可以使用Pair.of()代替new SimpleEntry<>,然后使用Pair::getKeyPair::getValue - Tobias Grunwald

7
我想简要提醒一下Johnny Willer的回答。 Collectors.toMap 依赖于 Map.merge,并且不接受空值,因此会抛出一个 NullPointerException。这在这个错误报告中有所描述: https://bugs.openjdk.java.net/browse/JDK-8148463 同时,如果一个键出现多次,普通的 Collectors.toMap 将会抛出一个 IllegalStateException
Java 8 上通过构建器语法编写一个基于 HashMap 的自定义收集器是获取包含 null 值的 Map 的另一种方法(因为它允许空值)。
Map<String, String> myMap = Stream.of(
         new SimpleEntry<>("key1", "value1"),
         new SimpleEntry<>("key2", (String) null),
         new SimpleEntry<>("key3", "value3"),
         new SimpleEntry<>("key1", "value1updated"))
        .collect(HashMap::new,
                (map, entry) -> map.put(entry.getKey(),
                                        entry.getValue()),
                HashMap::putAll);

1
使用entry(...)new SimpleEntry<>(...)更简单(导入java.util.Map.entry - Mugen

3
我们使用一个简单的实用类以流畅的方式初始化地图:
Map<String, String> map = MapInit
    .init("key1", "value1")
    .put("key2", "value2")
    .put("key3", "value3")
    .getMap();

这个实用类在键和值的类型、条目数量以及生成的 Map 的类型方面都没有限制。

该实用类如下所示:

public class MapInit<K, V, T extends Map<K, V>> {
    private final T map;


    private MapInit(final T map) {
        this.map = map;
    }

    public T getMap() {
        return this.map;
    }

    public static <K, V> MapInit<K, V, HashMap<K, V>> init(final K key, final V value) {
        return init(HashMap::new, key, value);
    }

    public static <K, V, T extends Map<K, V>> MapInit<K, V, T> init(final Supplier<T> mapSupplier, final K key, final V value) {
        return new MapInit<>(mapSupplier.get()) //
                .put(key, value);
    }

    public MapInit<K, V, T> put(final K key, final V value) {
        this.map.put(key, value);
        return this;
    }
}

你是自己发明的吗?请不要这样做。已经存在更短更好的解决方案,例如在https://www.baeldung.com/java-initialize-hashmap和本页面上。 - MarkHu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接