有没有这样初始化Java HashMap的方法?:
Map<String,String> test =
new HashMap<String, String>{"test":"test","test":"test"};
正确的语法是什么?我没有发现任何与此相关的信息。这可能吗?我正在寻找最短/最快的方法来将一些“最终/静态”值放在地图中,这些值永远不会改变,并且在创建地图时预先知道。
有没有这样初始化Java HashMap的方法?:
Map<String,String> test =
new HashMap<String, String>{"test":"test","test":"test"};
正确的语法是什么?我没有发现任何与此相关的信息。这可能吗?我正在寻找最短/最快的方法来将一些“最终/静态”值放在地图中,这些值永远不会改变,并且在创建地图时预先知道。
所有版本
如果您恰好需要一个条目: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")
);
在上面的示例中,test和test2都是相同的,只是表示Map的方式不同。Map.of方法最多为映射中的十个元素定义,而Map.ofEntries方法没有这样的限制。
注意,在这种情况下,生成的映射将是一个不可变的映射。如果希望映射是可变的,可以再次复制它,例如使用mutableMap=newHashMap<>(map.of(“a”,“b”));。还要注意,在这种情况下,键和值不能为空。
(另见JEP 269和Javadoc)
对于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个条目。否则,使用builder:source。
Map<String,String> test = new HashMap<String, String>()
{
{
put(key1, value1);
put(key2, value2);
}
};
没有直接的方法可以做到这一点——截至2021,Java没有Map文字(然而,我认为它们是为Java8提出的,但没有实现)。
有些人喜欢这样:
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");
如果测试映射是实例变量,请将初始化放在构造函数或实例初始值设定项中:
Map<String,String> test = new HashMap<String, String>();
{
test.put("test","test");
test.put("test1","test2");
}
如果测试映射是类变量,请将初始化放在静态初始值设定项中:
static Map<String,String> test = new HashMap<String, String>();
static {
test.put("test","test");
test.put("test1","test2");
}
如果你希望你的映射永远不变,你应该在初始化后用Collections.unmodifiedMap(…)包装你的映射。你也可以在静态初始化器中这样做:
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);
}
(我不确定你现在是否能让考试成为最终……试试看,然后在这里报告。)
从Java9开始,您还拥有Map.of(…)和Map.entries()语法,正如yankee的回答所解释的。
如果您允许第三方库,您可以使用Guava的ImmutableMap来实现字面上的简洁:
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关于其不可变集合类型的用户指南文章
另一种方法是使用纯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");
tl;博士
在Java 9和更高版本中使用Map.of…方法。
Map< String , String > animalSounds =
Map.of(
"dog" , "bark" , // key , value
"cat" , "meow" , // key , value
"bird" , "chirp" // key , value
)
;
地图of
Java9添加了一系列Map.of静态方法,以满足您的需要:使用文本语法实例化不可变的Map。
映射(条目集合)是不可变的,因此在实例化后不能添加或删除条目。此外,每个条目的键和值是不可变的,不能更改。有关其他规则,请参见Javadoc,例如不允许NULL,不允许重复键,映射的迭代顺序是任意的。
让我们看看这些方法,使用一些示例数据绘制一张一周中某一天的地图,以显示我们希望该天工作的人。
Person alice = new Person( "Alice" );
Person bob = new Person( "Bob" );
Person carol = new Person( "Carol" );
映射.of()
Map.of创建一个空的Map。无法修改,因此无法添加条目。下面是这样一个地图的示例,空的没有条目。
Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();
dailyWorkerEmpty.toString():{}
第页,共页(…)
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”},星期六=人{name=“虱子”}}
项目地图(…)
条目的Map.Entries(Map.Entry,…)采用实现Map.Entrey接口的任意数量的对象。Java捆绑了两个实现该接口的类,一个是可变的,另一个是不可变的:AbstractMap.SimpleEntry,AbstractMap.SimpleModuleEntry。但是我们不需要指定具体的类。我们只需要调用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():{星期三=人{name=‘Bob’},星期二=人{name=“Bob”},周四=人{name=‘Carol’}、星期五=人{name=‘Carol‘},周一=人{name=‘虱子’}
地图副本
Java10添加了Map.copyOf方法。传递现有的映射,获取该映射的不可变副本。
笔记
注意,不能保证通过Map.of生成的映射的迭代器顺序。条目具有任意顺序。不要根据看到的顺序编写代码,因为文档警告顺序可能会更改。
注意,所有这些Map.of…方法都返回未指定类的Map。底层的具体类甚至可能因Java的不同版本而异。这种匿名性使Java能够从各种实现中选择最适合您特定数据的实现。例如,如果您的键来自枚举,Java可能会在封面下使用EnumMap。
您可以通过两种简单的方式轻松地创建自己的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;
}
}
并像使用新的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;
}
}
并像新的MapBuilder<String,Object>().put(“a”,1).pput(“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=Map.of(p(“a”,1),p(“b”,“foo”);
(人们可以想象使用Map.Entry来代替Pair,但由于它是一个接口,所以它需要一个实现类和/或一个助手工厂方法。它也不是不可变的,并且包含对该任务不有用的其他逻辑。)
您可以在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
JAVA 8
在普通的java8中,您还可以使用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;
当然,正如在其他答案中所指出的,在java9之后,您有更简单的方法来实现同样的目的。
使用Java 8或更低版本
您可以使用静态块用一些值初始化映射。例子:
public static Map<String,String> test = new HashMap<String, String>
static {
test.put("test","test");
test.put("test1","test");
}
使用Java 9或更高版本
在声明时,可以使用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;
}
//call
Map<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,不需要null值,因此它将抛出NullPointerException,如本错误报告中所述:https://bugs.openjdk.java.net/browse/JDK-8148463
此外,如果键出现多次,默认的Collectors.toMap将抛出IllegalStateException。
使用Java 8上的构建器语法获取具有空值的映射的另一种方法是编写一个由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);
以下代码可以在Java 8中实现此功能:
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(),因为你需要一个不同类型的映射。
但是,如果你感觉很活泼,你可以使用Java 8供应商(不推荐)。
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();
或者制作自己的功能界面(我觉得不错):
@FunctionalInterface
public 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);
});
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(..)构造函数创建的映射不允许键和值都为空。
我们使用一个简单的实用程序类以流畅的方式初始化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");
}
对于使用流的Java 8
Map<String, String> map = Stream.of(new String[][] {
{ "Hello", "World" },
{ "John", "Doe" },
}).collect(Collectors.toMap(data -> data[0], data -> data[1]));