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


1090

有没有办法像这样初始化Java HashMap?

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

正确的语法是什么?我还没有发现任何有关此的信息。这可能吗?我正在寻找在地图中放置一些“最终/静态”值的最短/最快方法,这些值永远不会改变,并且在创建地图时会事先知道。



密切相关:stackoverflow.com/questions/507602/…(两个问题都涉及初始化带有静态最终值的常数映射。)
Jonik



如果你使用apache.commons.collections,您可以使用commons.apache.org/proper/commons-collections/javadocs/...
斧头。

Answers:


1341

所有版本

如果您碰巧只需要一个条目:有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.of最多可为地图中的十个元素定义该方法,而该Map.ofEntries方法没有此限制。

请注意,在这种情况下,生成的地图将是不可变的地图。如果您希望地图可变,则可以再次复制它,例如使用mutableMap = new HashMap<>(Map.of("a", "b"));

(另请参见JEP 269Javadoc

对于Java版本8:

不,您将必须手动添加所有元素。您可以在匿名子类中使用初始化程序,以使语法短一些:

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

但是,在某些情况下,匿名子类可能会引入不需要的行为。例如,这包括:

  • 它会生成一个附加类,从而增加内存消耗,磁盘空间消耗和启动时间
  • 对于非静态方法:它包含对创建方法所调用的对象的引用。这意味着在仍引用创建的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

9
@Michael:是的,如果要使用功能,则不能使用非功能。但是为什么要呢?
洋基

6
对于需要单个条目的地图的情况是Collections.singletonMap():)
skwisgaar

3
现在已经发布了稳定的Java 9,我更喜欢Javadoc的此链接。+1是因为少了一个依赖!
富兰克林·于

3
Java 9在哪里entry记录?
nobar

1029

这是一种方式。

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

72
它可以工作,但是很丑陋,并且具有用户在操作之前应了解的无形的副作用-例如,当场生成整个匿名类。
jprete 2011年

96
是的,这就是我写关于谨慎的方式,并提供了指向描述的链接。
gregory561

6
很棒的链接。指向GreencoddsTenthRuleOfProgramming的链接中的引用值得一读。
michaelok

19
您可以添加“ as ImmutableMap.builder.put(” k1“,” v1“)。put(” k2“,” v2“)。build()”,因为“ of”方法最多限制为5对吗?
kommradHomer 2014年


341

如果您允许第3方库,则可以使用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用户指南文章

26
此外,番石榴还具有ImmutableMap.builder.put(“ k1”,“ v1”)。put(“ k2”,“ v2”)。build();
Xetius

16
ImmutableMap与HashMap不同,因为它对空值将失败,而映射HashMap不会。
Gewthen 2014年

2
只是为了帮助可能会遇到此问题的其他人。您必须键入构建器以使其成为Map <String,String>,如下所示:Map <String,String> test = ImmutableMap。<String,String> builder()。put(“ k1”,“ v1”)。 put(“ k2”,“ v2”)。build();
Thiago 2015年

这真棒!
gaurav

105

没有直接的方法可以做到-Java没有Map文字(但是-我认为它们是为Java 8提出的)。

有些人喜欢这样:

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

这将创建HashMap的匿名子类,其实例初始化程序将这些值放入。(顺便说一句,地图不能包含相同值的两倍,您的第二个放置权将覆盖第一个放置。在下一个示例中,我将使用不同的值。)

正常的方式是这样的(对于局部变量):

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最终决定……尝试一下并在此处报告。)


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

简单明了。我认为对此进行扩展评论部分将是最佳答案。
ooolala

15
但是,有一些内存含义应注意。blog.jooq.org/2014/12/08/…–
Amalgovinus

1
@Amalgovinus基本上,通过创建一个新的子类,您正在将类型自变量硬编码HashMap到该子类中。仅当您实际提供它们时,此方法才有效。(使用新的(空)HashMap,类型参数将不相关。)
PaŭloEbermann,2016年

1
我喜欢它的清洁性,但是它创建了不必要的匿名类,并且有此处描述的问题: c2.com/cgi/wiki?DoubleBraceInitialization
udachny

1
@hello_its_me:因为它与stackoverflow.com/a/6802512/1386911答案相同,只是格式不同。并且在这种情况下,这种扩展格式除了具有紧凑性以外,在可读性方面没有任何附加价值。
丹尼尔·哈里(DanielHári)

44

使用纯Java 7类和varargs的替代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");

我写的你的灵感来自一个答案:stackoverflow.com/questions/507602/...
GeroldBroser恢复了莫妮卡

1
使用以前的Java版本,从未提及但可读的Apache Utils解决方案:MapUtils.putAll(new HashMap <String,String>(),new Object [] {“ My key”,“ my value”,...
Rolintocour

3

tl; dr

使用Map.of…Java 9及更高版本中的方法。

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

WeekendWorker.toString():{SUNDAY =人{name ='Bob'},SATURDAY =人{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.of保证。条目具有任意顺序。不要根据所看到的顺序来编写代码,因为文档警告该顺序可能会更改。

请注意,所有的这些Map.of…方法返回Map未指定类别的。底层的具体类甚至可能从一个Java版本到另一个Java版本都不同。这种匿名性使Java可以从各种实现中进行选择,无论哪种方式都可以最适合您的特定数据。例如,如果您的密钥来自枚举,则Java可能会在幕后使用EnumMap


1

您可以通过Map.of2种简单的方法轻松地制作自己的方法(仅在Java 9及更高版本中可用)

用一定数量的参数制作

public <K,V> Map<K,V> mapOf(K k1, V v1, K k2, V v2 /* perhaps more parameters */) {
    return new HashMap<K, V>() {{
      put(k1, v1);
      put(k2,  v2);
      // etc...
    }};
}

使用清单制作

您也可以使用列表来完成此操作,而不是为一组特定的参数设置很多方法。

public <K, V> Map<K, V> mapOf(List<K> keys, List<V> values) {
   if(keys.size() != values.size()) {
        throw new IndexOutOfBoundsException("amount of keys and values is not equal");
    }

    return new HashMap<K, V>() {{
        IntStream.range(0, keys.size()).forEach(index -> put(keys.get(index), values.get(index)));
    }};
}

注意 不建议将其用于所有内容,因为每次使用时都会创建一个匿名类。


1

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及更高版本中,您可以使用更简单的方法来进行此操作。


0

不幸的是,如果键和值的类型不同,则使用varargs不太合理,因为您将不得不使用Object...并且完全失去类型安全性。如果您总是要创建例如a Map<String, String>,则当然可以创建a ,toMap(String... args)但不是很漂亮,因为很容易将键和值混淆,并且奇数个参数将无效。

您可以创建具有可链接方法的HashMap子类,例如

public class ChainableMap<K, V> extends HashMap<K, V> {
  public ChainableMap<K, V> set(K k, V v) {
    put(k, v);
    return this;
  }
}

并像这样使用 new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

另一种方法是使用通用的构建器模式:

public class MapBuilder<K, V> {
  private Map<K, V> mMap = new HashMap<>();

  public MapBuilder<K, V> put(K k, V v) {
    mMap.put(k, v);
    return this;
  }

  public Map<K, V> build() {
    return mMap;
  }
}

并像这样使用 new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

但是,我现在使用的解决方案随后利用了varargs和此类Pair

public class Maps {
  public static <K, V> Map<K, V> of(Pair<K, V>... pairs) {
    Map<K, V> = new HashMap<>();

    for (Pair<K, V> pair : pairs) {
      map.put(pair.first, pair.second);
    }

    return map;
  }
}

Map<String, Object> map = Maps.of(Pair.create("a", 1), Pair.create("b", "foo");

冗长的Pair.create()细节让我有些困扰,但这很好。如果您不介意静态导入,则当然可以创建一个帮助器:

public <K, V> Pair<K, V> p(K k, V v) {
  return Pair.create(k, v);
}

Map<String, Object> map = Maps.of(p("a", 1), p("b", "foo");

(而不是Pair人们想象的使用Map.Entry,但是由于它是一个接口,因此它需要一个实现类和/或一个辅助工厂方法。它也不是不变的,还包含其他对该任务无用的逻辑。)


0

您可以在Java 8中使用Streams(这是Set的示例):

@Test
public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
    Set<String> countries = Stream.of("India", "USSR", "USA")
      .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

    assertTrue(countries.contains("India"));
}

参考:https : //www.baeldung.com/java-double-brace-initialization


0

如果只需要放置一对键值对,则可以使用Collections.singletonMap(key,value);。


1
格式代码使帖子更具可读性
Renato
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.