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

有没有像这样初始化JavaHashMap的方法?:

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

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

1699960 次浏览

所有版本

如果你碰巧只需要一个条目:有Collections.singletonMap("key", "value")

Java版本9或更高:

是的,现在这是可能的。在Java9中,添加了几个工厂方法来简化映射的创建:

// 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中最多十个元素,而Map.ofEntries方法没有这样的限制。

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

(参见jep269Javadoc

Java版本8:

不,你必须手动添加所有元素。你可以在匿名子类中使用初始化器来缩短语法:

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

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

  • 它生成一个额外的类,增加内存消耗、磁盘空间消耗和启动时间
  • 在非静态方法的情况下:它保存对调用创建方法的对象的引用。这意味着在创建的映射对象仍被引用时,无法垃圾收集外部类的对象,从而阻塞额外的内存

使用函数进行初始化也将使您能够在初始化器中生成映射,但避免了令人讨厌的副作用:

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

这是一种方法。

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个条目。否则,使用构建器:来源

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

没有直接的方法可以做到这一点-截至2021年,Java没有地图文字(然而-我认为它们是Java8提出的,但没有成功)。

有些人喜欢这样:

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

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

通常的方法是这样的(对于局部变量):

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

如果你希望你的map永远不会改变,你应该在初始化后用Collections.unmodifiableMap(...)包装你的map。你也可以在静态初始化器中这样做:

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最终……尝试一下并在这里报告。

从Java9开始,您还可以使用Map.of(…)和Map.of项()语法,如洋基队的答案中所述。

如果您允许第三方库,您可以使用番石榴不可变类型来实现类似文字的简洁:

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的不可变类型实现不同于Java的HashMap实现(最值得注意的是它是不可变的,不允许空键/值)
  • 有关更多信息,请参阅Guava不可变集合类型上的用户指南文章

另一种选择,使用普通Java7类和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");

tl; dr

在Java9及更高版本中使用Map.of…方法。

Map< String , String > animalSounds =Map.of("dog"  , "bark" ,   // key , value"cat"  , "meow" ,   // key , value"bird" , "chirp"    // key , value);

Map.of

Java9添加了一系列#0静态方法来执行您想要的操作:使用文字语法实例化不可变#1

映射(条目的集合)是不可变的,因此您不能在实例化后添加或删除条目。此外,每个条目的键和值是不可变的,不能更改。查看Javadoc用于其他规则,例如不允许空值,不允许重复键,映射的迭代顺序是任意的。

让我们看看这些方法,使用一些示例数据为我们预计将在当天工作的人绘制每周一天的地图。

Person alice = new Person( "Alice" );Person bob = new Person( "Bob" );Person carol = new Person( "Carol" );

Map.of()

#0创建了一个空的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 , valueDayOfWeek.SUNDAY , bob           // key , value);

weekendWorker.toString():{SUNday=Person{name='Bob'}, SATURday=Person{name='Alice'}}

Map.ofEntries( … )

#0接受任意数量的实现#1接口的对象。Java捆绑了两个实现该接口的类,一个是可变的,另一个是不可变的:#2#3。但是我们不需要指定一个具体类。我们只需要调用Map.Entry0方法,传递我们的键和值,我们就可以得到一个实现#1接口的类的对象。

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():{星期三=人{名字='Bob'},星期二=人{名字='Bob'},星期四=人{名字='Carol'},星期五=人{名字='Carol'},星期一=人{名字='Alice'}}

Map.copyOf

Java10添加了方法#0。传递一个现有的map,取回该map的不可变副本。

备注

请注意,通过Map.of生成的映射的迭代器顺序是没有保证的。条目具有任意顺序。不要根据看到的顺序编写代码,因为留档警告顺序可能会更改。

请注意,所有这些Map.of…方法都返回未指定的类中的#1。底层的具体类甚至可能因Java的不同版本而异。这种匿名性使Java能够从各种实现中进行选择,无论哪种实现最适合您的特定数据。例如,如果您的密钥来自enum,Java可能会暗中使用#2

您可以通过2种简单的方法轻松制作自己的Map.of(仅适用于Java9及更高版本)方法

用一定数量的参数来做

示例

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

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

不幸的是,如果键和值的类型不相同,则使用varargs不是很合理,因为您必须使用Object...并完全失去类型安全性。如果您总是想创建例如Map<String, String>,当然可以创建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");

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

您可以在Java8中使用Streams(这是Set的例子):

@Testpublic 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

java8

在纯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之后,您有更简单的方法来做同样的事情。

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

Java8或更少

您可以使用静态块使用一些值初始化映射。示例:

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

Java9或以上

您可以使用Map.of()方法在声明时使用一些值初始化映射。示例:

public static Map<String,String> test = Map.of("test","test","test1","test");

您可以创建一个方法来初始化映射,如下例所示:

Map<String, Integer> initializeMap(){Map<String, Integer> ret = new HashMap<>();
//populate ret...
return ret;}
//callMap<String, Integer> map = initializeMap();

我们可以使用具有SimpleEntry的AbstractMap类,它允许创建不可变映射

Map<String, String> map5 = Stream.of(new AbstractMap.SimpleEntry<>("Sakshi","java"),new AbstractMap.SimpleEntry<>("fine","python")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));    
System.out.println(map5.get("Sakshi"));map5.put("Shiva", "Javascript");System.out.println(map5.get("Shiva"));// here we can add multiple entries.

我想给约翰尼·威勒的回答一个简短的警告。

Collectors.toMap依赖于Map.merge不期望空值,因此它将抛出一个NullPointerException,如bug报告所述:https://bugs.openjdk.java.net/browse/JDK-8148463

此外,如果一个键出现多次,默认的Collectors.toMap将抛出一个IllegalStateException

使用Java8上的构建器语法获取空值映射的另一种方法是编写自定义收集器由HashMap支持(因为它确实允许空值):

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

以下代码可以在Java8中做到这一点:

Map<String, Integer> map = Stream.of(new Object[][] \{\{ "data1", 1 },{ "data2", 2 },}).collect(Collectors.toMap(data -> (String) data[0], data -> (Integer) data[1]));

学分

简单的方法来做到这一点:

public static Map<String, String> mapWithValues(String...values) {Map<String, String> map = new HashMap<String, String>();    
for (int x = 0; x < values.length; x = x+2) {map.put(values[x], values[x+1]);}    
return map;}

如果它是一个实例变量,那么实例初始化块肯定是要走的路,特别是如果你不能使用Map.of(),因为你需要不同类型的映射。

但如果你感觉活泼,你可以使用Java8Supplier(不推荐)。

private final Map<String,Runnable> games = ((Supplier<Map<String,Runnable>>)() -> {Map<String,Runnable> map = new LinkedHashMap<>();
map.put("solarus",this::playSolarus);map.put("lichess",this::playLichess);
return map;}).get();

或者制作自己的功能界面(对我来说看起来不错):

@FunctionalInterfacepublic interface MapMaker<M> {static <M extends Map<K,V>,K,V> M make(M map,MapMaker<M> maker) {maker.build(map);return map;}
void build(M map);}
// Can use LinkedHashMap!private final Map<String,Runnable> games = MapMaker.make(new LinkedHashMap<>(),(map) -> {map.put("solarus",this::playSolarus);map.put("lichess",this::playLichess);});

Map.of()似乎最通用且限制最少。在这里,它自动处理非对象输入值:

List<Map<String, Object> certs = new ArrayList<>()\{\{ add( Map.of("TAG",          Obj1 // Object"TAGGED_ID",    1L //Long"DEGREE",       "PARENT" // String"DATE",         LocalDate.now() // LocalDate));}};

请注意,由静态Map.of(..)构造器创建的映射不允许键和值为null

我们使用一个简单的实用程序类以流畅的方式初始化Maps:

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

我发现Baeldung的一篇伟大的文章列出了在不同Java版本中执行此操作的几种方法。

有几个有趣的方法可以很方便

对于任何Java版本

public static Map<String, String> articleMapOne;static {articleMapOne = new HashMap<>();articleMapOne.put("ar01", "Intro to Map");articleMapOne.put("ar02", "Some article");}

对于使用流的Java8

Map<String, String> map = Stream.of(new String[][] \{\{ "Hello", "World" },{ "John", "Doe" },}).collect(Collectors.toMap(data -> data[0], data -> data[1]));