您将如何在 Java 中初始化静态 Map
?
方法一:静态初始化方法二:实例初始化(匿名子类)还是其他方法?
各自的优缺点是什么?
以下是说明这两种方法的示例:
import java.util.HashMap;
import java.util.Map;
public class Test {
private static final Map<Integer, String> myMap = new HashMap<>();
static {
myMap.put(1, "one");
myMap.put(2, "two");
}
private static final Map<Integer, String> myMap2 = new HashMap<>(){
{
put(1, "one");
put(2, "two");
}
};
}
在这种情况下,实例初始化程序只是语法糖,对吧?我不明白为什么你需要一个额外的匿名类来初始化。如果正在创建的类是最终的,它将不起作用。
您也可以使用静态初始化器创建不可变映射:
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);
}
}
我喜欢初始化静态、不可变映射的 Guava 方式:
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 的不可变收集实用程序的优势,请参阅 Immutable Collections Explained in Guava User Guide。
Guava 的(一个子集)曾经被称为 Google Collections。如果您还没有在您的 Java 项目中使用这个库,我强烈建议您尝试一下! Guava 已迅速成为 Java 最流行和最有用的免费 3 方库之一,如 fellow SO users agree。 (如果您是新手,该链接后面有一些优秀的学习资源。)
更新(2015 年):至于 Java 8,我仍然会使用 Guava 方法,因为它比其他任何方法都干净。如果您不想依赖 Guava,请考虑使用 plain old init method。如果您问我,使用 two-dimensional array and Stream API 的 hack 非常难看,如果您需要创建一个其键和值类型不同的 Map(如问题中的 Map<Integer, String>
),则会变得更难看。
至于 Guava 的总体未来,关于 Java 8,Louis Wasserman said this 早在 2014 年,[update] 于 2016 年宣布Guava 21 will require and properly support Java 8。
更新(2016 年):作为 Tagir Valeev points out,Java 9 最终将通过为集合添加 convenience factory methods 来使这一切变得干净,只使用纯 JDK:
static final Map<Integer, String> MY_MAP = Map.of(
1, "one",
2, "two"
);
我会使用:
public class Test {
private static final Map<Integer, String> MY_MAP = createMap();
private static Map<Integer, String> createMap() {
Map<Integer, String> result = new HashMap<>();
result.put(1, "one");
result.put(2, "two");
return Collections.unmodifiableMap(result);
}
}
它避免了匿名类,我个人认为这是一种不好的风格,并避免它使地图的创建更加明确它使地图不可修改,因为 MY_MAP 是常量,我将其命名为常量
Java 5 提供了这种更紧凑的语法:
static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
put("Up", "Down");
put("Charm", "Strange");
put("Top", "Bottom");
}};
HashMap implements Serializable
。由于您实际上使用此“技巧”创建了 HashMap 的子类,因此您隐式地创建了一个 Serializable 类。为此,您应该提供一个serialUID。
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
HashMap.equals
在 AbstractMap
中定义,适用于 Map 的 any 子类,因此这里不必担心。钻石操作员的事情很烦人,但如前所述,现在已经解决了。
第二种方法的一个优点是您可以用 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!
Java 9+ 中的 Map.of
private static final Map<Integer, String> MY_MAP = Map.of(1, "one", 2, "two");
有关详细信息,请参阅 JEP 269。 JDK 9 在 2017 年 9 月达到了 general availability。
Map.ofEntries
这是一个 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
String[][]
不会这样做,需要 Object[][]
)。恕我直言,这种方法很丑陋(演员阵容更是如此)并且很难记住;我自己不会使用它。
爪哇 9
我们可以使用 Map.ofEntries
,调用 Map.entry( k , v )
来创建每个条目。
import static java.util.Map.entry;
private static final Map<Integer,String> map = Map.ofEntries(
entry(1, "one"),
entry(2, "two"),
entry(3, "three"),
entry(4, "four"),
entry(5, "five"),
entry(6, "six"),
entry(7, "seven"),
entry(8, "eight"),
entry(9, "nine"),
entry(10, "ten"));
我们也可以按照 Tagir 在他的回答 here 中的建议使用 Map.of
,但使用 Map.of
的条目不能超过 10 个。
爪哇 8
我们可以创建一个映射条目流。我们已经在 java.util.AbstractMap
中实现了两个 Entry
,它们是 SimpleEntry 和 SimpleImmutableEntry。对于此示例,我们可以将前者用作:
import java.util.AbstractMap.*;
private static final Map<Integer, String> myMap = Stream.of(
new SimpleEntry<>(1, "one"),
new SimpleEntry<>(2, "two"),
new SimpleEntry<>(3, "three"),
new SimpleEntry<>(4, "four"),
new SimpleEntry<>(5, "five"),
new SimpleEntry<>(6, "six"),
new SimpleEntry<>(7, "seven"),
new SimpleEntry<>(8, "eight"),
new SimpleEntry<>(9, "nine"),
new SimpleEntry<>(10, "ten"))
.collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
new SimpleEntry<>()
方式的可读性远不如静态 put()
:/
使用 Eclipse Collections,以下所有操作都将起作用:
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 的提交者
在这种情况下,我永远不会创建匿名子类。如果您想让地图不可修改,静态初始化器同样可以工作,例如:
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);
}
我喜欢匿名类,因为它很容易处理:
public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
{
put(1, "some value");
//rest of code here
}
});
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);
}
}
如果我们声明了多个常量,那么该代码将被编写在静态块中,并且将来很难维护。所以最好使用匿名类。
public class Test {
public static final Map numbers = Collections.unmodifiableMap(new HashMap(2, 1.0f){
{
put(1, "one");
put(2, "two");
}
});
}
并且建议将 unmodifiableMap 用于常量,否则它不能被视为常量。
我强烈建议使用“双括号初始化”样式而不是静态块样式。
有人可能会评论说他们不喜欢匿名类、开销、性能等。
但我更多考虑的是代码的可读性和可维护性。从这个角度来看,我认为双大括号是一种更好的代码风格而不是静态方法。
元素是嵌套的和内联的。它更多是面向对象的,而不是程序性的。性能影响非常小,可以忽略不计。更好的 IDE 大纲支持(而不是许多匿名静态{} 块)您保存了几行注释以使它们建立关系。防止异常和字节码优化器可能导致未初始化对象的元素泄漏/实例引导。不用担心静态块的执行顺序。
另外,如果你知道匿名类的 GC,你可以随时使用 new HashMap(Map map)
将其转换为普通的 HashMap。
你可以这样做,直到你遇到另一个问题。如果你这样做,你应该为它使用完整的另一种编码风格(例如,没有静态,工厂类)。
像往常一样 apache-commons 有正确的方法 MapUtils.putAll(Map, Object[]):
例如,要创建一个颜色图:
Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
{"RED", "#FF0000"},
{"GREEN", "#00FF00"},
{"BLUE", "#0000FF"}
});
Arrays.asMap( ... )
,我认为这是最好的解决方案。重新发明轮子通常是愚蠢的。非常轻微的缺点是,对于泛型,它将需要未经检查的转换。
Map<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
之类的行SuppressWarnings( unchecked )
String[][]
我也会收到“警告”!当然,这仅在您的 K
和 V
是同一类时才有效。我认为您没有(可以理解)在 Eclipse 设置中将“未经检查的转换”设置为“忽略”?
这是我最喜欢的,如果我
不想(或不能)使用 Guava 的 ImmutableMap.of()
或者我需要一个可变的地图
或者我需要超过 JDK9+ 中 Map.of() 的 10 个条目限制
public static <A> Map<String, A> asMap(Object... keysAndValues) {
return new LinkedHashMap<String, A>() {{
for (int i = 0; i < keysAndValues.length - 1; i++) {
put(keysAndValues[i].toString(), (A) keysAndValues[++i]);
}
}};
}
它非常紧凑,它忽略了杂散值(即没有值的最终键)。
用法:
Map<String, String> one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal");
Map<String, Object> two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2));
我更喜欢使用静态初始化程序来避免生成匿名类(没有其他目的),所以我将列出使用静态初始化程序初始化的提示。所有列出的解决方案/提示都是类型安全的。
注意:该问题并未说明有关使地图不可修改的任何内容,因此我将省略这一点,但知道使用 Collections.unmodifiableMap(map)
可以轻松完成。
第一个提示
第一个技巧是您可以对地图进行本地引用,并给它一个简短的名称:
private static final Map<Integer, String> myMap = new HashMap<>();
static {
final Map<Integer, String> m = myMap; // Use short name!
m.put(1, "one"); // Here referencing the local variable which is also faster!
m.put(2, "two");
m.put(3, "three");
}
第二个提示
第二个技巧是你可以创建一个辅助方法来添加条目;如果您愿意,还可以公开此辅助方法:
private static final Map<Integer, String> myMap2 = new HashMap<>();
static {
p(1, "one"); // Calling the helper method.
p(2, "two");
p(3, "three");
}
private static void p(Integer k, String v) {
myMap2.put(k, v);
}
此处的辅助方法不可重复使用,因为它只能将元素添加到 myMap2
。为了使其可重用,我们可以将映射本身作为辅助方法的参数,但初始化代码不会更短。
第三个提示
第三个技巧是您可以创建一个可重用的类似构建器的辅助类,并具有填充功能。这实际上是一个简单的 10 行帮助类,它是类型安全的:
public class Test {
private static final Map<Integer, String> myMap3 = new HashMap<>();
static {
new B<>(myMap3) // Instantiating the helper class with our map
.p(1, "one")
.p(2, "two")
.p(3, "three");
}
}
class B<K, V> {
private final Map<K, V> m;
public B(Map<K, V> m) {
this.m = m;
}
public B<K, V> p(K k, V v) {
m.put(k, v);
return this; // Return this for chaining
}
}
如果你想要不可修改的地图,最后 java 9 添加了一个很酷的工厂方法 of
到 Map
接口。类似的方法也被添加到 Set、List 中。
Map<String, String> unmodifiableMap = Map.of("key1", "value1", "key2", "value2");
您正在创建的匿名类运行良好。但是您应该知道这是一个 inner 类,因此,它将包含对周围类实例的引用。所以你会发现你不能用它做某些事情(使用 XStream 做一件事)。你会得到一些非常奇怪的错误。
话虽如此,只要您知道,那么这种方法就可以了。我大部分时间都用它来以简洁的方式初始化各种集合。
编辑:在评论中正确指出这是一个静态类。显然我没有仔细阅读这个。但是我的评论仍然适用于匿名内部类。
如果您想要一些简洁且相对安全的东西,您可以将编译时类型检查转移到运行时:
static final Map<String, Integer> map = MapUtils.unmodifiableMap(
String.class, Integer.class,
"cat", 4,
"dog", 2,
"frog", 17
);
此实现应捕获任何错误:
import java.util.HashMap;
public abstract class MapUtils
{
private MapUtils() { }
public static <K, V> HashMap<K, V> unmodifiableMap(
Class<? extends K> keyClazz,
Class<? extends V> valClazz,
Object...keyValues)
{
return Collections.<K, V>unmodifiableMap(makeMap(
keyClazz,
valClazz,
keyValues));
}
public static <K, V> HashMap<K, V> makeMap(
Class<? extends K> keyClazz,
Class<? extends V> valClazz,
Object...keyValues)
{
if (keyValues.length % 2 != 0)
{
throw new IllegalArgumentException(
"'keyValues' was formatted incorrectly! "
+ "(Expected an even length, but found '" + keyValues.length + "')");
}
HashMap<K, V> result = new HashMap<K, V>(keyValues.length / 2);
for (int i = 0; i < keyValues.length;)
{
K key = cast(keyClazz, keyValues[i], i);
++i;
V val = cast(valClazz, keyValues[i], i);
++i;
result.put(key, val);
}
return result;
}
private static <T> T cast(Class<? extends T> clazz, Object object, int i)
{
try
{
return clazz.cast(object);
}
catch (ClassCastException e)
{
String objectName = (i % 2 == 0) ? "Key" : "Value";
String format = "%s at index %d ('%s') wasn't assignable to type '%s'";
throw new IllegalArgumentException(String.format(format, objectName, i, object.toString(), clazz.getSimpleName()), e);
}
}
}
在 Java 8 中,我开始使用以下模式:
private static final Map<String, Integer> MAP = Stream.of(
new AbstractMap.SimpleImmutableEntry<>("key1", 1),
new AbstractMap.SimpleImmutableEntry<>("key2", 2)
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
这不是最简洁和有点迂回的,但是
它不需要 java.util 之外的任何东西
它是类型安全的,并且很容易适应不同类型的键和值。
toMap
签名来指定地图的类型。
您的第二种方法 (双括号初始化) 被认为是 anti pattern,所以我会采用第一种方法。
初始化静态 Map 的另一种简单方法是使用此实用程序函数:
public static <K, V> Map<K, V> mapOf(Object... keyValues) {
Map<K, V> map = new HashMap<>(keyValues.length / 2);
for (int index = 0; index < keyValues.length / 2; index++) {
map.put((K)keyValues[index * 2], (V)keyValues[index * 2 + 1]);
}
return map;
}
Map<Integer, String> map1 = mapOf(1, "value1", 2, "value2");
Map<String, String> map2 = mapOf("key1", "value1", "key2", "value2");
注意:在 Java 9
中您可以使用 Map.of
我不喜欢静态初始化器语法,也不相信匿名子类。一般来说,我同意使用静态初始化程序的所有缺点以及使用之前答案中提到的匿名子类的所有缺点。另一方面 - 这些帖子中介绍的专业人士对我来说还不够。我更喜欢使用静态初始化方法:
public class MyClass {
private static final Map<Integer, String> myMap = prepareMap();
private static Map<Integer, String> prepareMap() {
Map<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "one");
hashMap.put(2, "two");
return hashMap;
}
}
我没有在任何答案中看到我使用(并且已经变得喜欢)的方法,所以这里是:
我不喜欢使用静态初始化器,因为它们很笨重,而且我不喜欢匿名类,因为它为每个实例创建一个新类。
相反,我更喜欢如下所示的初始化:
map(
entry("keyA", "val1"),
entry("keyB", "val2"),
entry("keyC", "val3")
);
不幸的是,这些方法不是标准 Java 库的一部分,因此您需要创建(或使用)一个定义以下方法的实用程序库:
public static <K,V> Map<K,V> map(Map.Entry<K, ? extends V>... entries)
public static <K,V> Map.Entry<K,V> entry(K key, V val)
(您可以使用“导入静态”来避免需要为方法名称添加前缀)
我发现为其他集合(list、set、sortedSet、sortedMap 等)提供类似的静态方法很有用
它不如 json 对象初始化那么好,但就可读性而言,这是朝着这个方向迈出的一步。
因为 Java 不支持映射文字,所以映射实例必须始终显式实例化和填充。
幸运的是,可以使用工厂方法来近似 Java 中映射字面量的行为。
例如:
public class LiteralMapFactory {
// Creates a map from a list of entries
@SafeVarargs
public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
LinkedHashMap<K, V> map = new LinkedHashMap<>();
for (Map.Entry<K, V> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
// Creates a map entry
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
return new AbstractMap.SimpleEntry<>(key, value);
}
public static void main(String[] args) {
System.out.println(mapOf(entry("a", 1), entry("b", 2), entry("c", 3)));
}
}
输出:
{a=1, b=2, c=3}
这比一次创建和填充地图元素要方便得多。
JEP 269 为 Collections API 提供了一些便利的工厂方法。该工厂方法不在当前的 Java 版本中,即 8,但计划在 Java 9 版本中发布。
对于 Map
,有两种工厂方法:of
和 ofEntries
。使用 of
,您可以传递交替的键/值对。例如,要创建像 {age: 27, major: cs}
这样的 Map
:
Map<String, Object> info = Map.of("age", 27, "major", "cs");
目前 of
有十个重载版本,因此您可以创建一个包含十个键/值对的映射。如果您不喜欢这种限制或交替键/值,您可以使用 ofEntries
:
Map<String, Object> info = Map.ofEntries(
Map.entry("age", 27),
Map.entry("major", "cs")
);
of
和 ofEntries
都将返回不可变的 Map
,因此您无法在构造后更改它们的元素。您可以使用 JDK 9 Early Access 试用这些功能。
嗯...我喜欢枚举;)
enum MyEnum {
ONE (1, "one"),
TWO (2, "two"),
THREE (3, "three");
int value;
String name;
MyEnum(int value, String name) {
this.value = value;
this.name = name;
}
static final Map<Integer, String> MAP = Stream.of( values() )
.collect( Collectors.toMap( e -> e.value, e -> e.name ) );
}
我已经阅读了答案,并决定编写自己的地图生成器。随意复制粘贴和享受。
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A tool for easy creation of a map. Code example:<br/>
* {@code MapBuilder.of("name", "Forrest").and("surname", "Gump").build()}
* @param <K> key type (inferred by constructor)
* @param <V> value type (inferred by constructor)
* @author Vlasec (for http://stackoverflow.com/a/30345279/1977151)
*/
public class MapBuilder <K, V> {
private Map<K, V> map = new HashMap<>();
/** Constructor that also enters the first entry. */
private MapBuilder(K key, V value) {
and(key, value);
}
/** Factory method that creates the builder and enters the first entry. */
public static <A, B> MapBuilder<A, B> mapOf(A key, B value) {
return new MapBuilder<>(key, value);
}
/** Puts the key-value pair to the map and returns itself for method chaining */
public MapBuilder<K, V> and(K key, V value) {
map.put(key, value);
return this;
}
/**
* If no reference to builder is kept and both the key and value types are immutable,
* the resulting map is immutable.
* @return contents of MapBuilder as an unmodifiable map.
*/
public Map<K, V> build() {
return Collections.unmodifiableMap(map);
}
}
编辑:最近,我经常发现公共静态方法 of
,我有点喜欢它。我将它添加到代码中并将构造函数设为私有,从而切换到静态工厂方法模式。
EDIT2:最近,我不再喜欢名为 of
的静态方法,因为它在使用静态导入时看起来很糟糕。我将其重命名为 mapOf
,使其更适合静态导入。
不定期副业成功案例分享