我怎样才能初始化一个静态的地图?

你将如何初始化Java中的静态地图?

方法一:静态初始化
方法二:实例初始化(匿名子类)还是其他一些方法?

各有什么优点和缺点?

这里是一个例子说明两种方法:

import java.util.HashMap; import java.util.Map; public class Test { private static final Map<Integer, String> myMap = new HashMap<Integer, String>(); static { myMap.put(1, "one"); myMap.put(2, "two"); } private static final Map<Integer, String> myMap2 = new HashMap<Integer, String>(){ { 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不可变集合实用程序的好处,请参阅Guava用户指南中的不可变集合解释

(的一个子集)番石榴曾经被称为谷歌收藏 。 如果你还没有在你的Java项目中使用这个库,我强烈建议尝试一下! 正如其他SO用户所认同的 ,Guava已经迅速成为Java最受欢迎和最有用的免费第三方库之一。 (如果你是新手,这个链接背后有一些很好的学习资源。)


更新(2015年) :至于Java 8 ,那么我仍然会使用番石榴的方法,因为它比任何其他的方式更清洁。 如果你不想要番石榴依赖,考虑一个普通的旧的init方法 。 如果你问我,那么使用二维数组和Stream API的攻击是相当丑陋的,如果你需要创建一个键和值不是相同类型的Map<Integer, String> (例如问题中的Map<Integer, String>Map<Integer, String>则会变得更丑。

至于Guava的未来,就Java 8而言,Louis Wasserman在2014年回顾了这个问题 ,并在2016年更新 ,宣布Guava 21将要求并正确地支持Java 8


更新(2016) :正如Tagir Valeev所指出的那样 , Java 9将最终通过为集合添加便捷工厂方法来清除干净,只使用纯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<Integer, String>(); result.put(1, "one"); result.put(2, "two"); return Collections.unmodifiableMap(result); } } 
  1. 它避免了匿名的类,我个人认为这是一个不好的风格,并避免
  2. 它使得地图的创建更加明确
  3. 它使地图不可修改
  4. 因为MY_MAP是不变的,我会把它命名为常量

Java 5提供了更紧凑的语法:

 static final Map<String , String> FLAVORS = new HashMap<String , String>() {{ put("Up", "Down"); put("Charm", "Strange"); put("Top", "Bottom"); }}; 

第二种方法的一个好处是你可以用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 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])); 

使用Eclipse Collections (以前的GS 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集合静态初始化原始地图。

 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集合的提交者

在Java 9中:

 private static final Map<Integer, String> MY_MAP = Map.of(1, "one", 2, "two"); 

详情请参阅JEP 269 。 JDK 9于2017年9月全面上市 。

在这种情况下我永远不会创建一个匿名的子类。 静态初始化器同样适用,如果您想使映射不可修改,例如:

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

也许看看Google Collections ,例如他们在他们页面上的视频,会很有趣。 他们提供了各种方式来初始化地图和集合,并提供不可变的集合。

更新:这个库现在被命名为番石榴 。

我喜欢匿名课,因为很容易处理它:

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

并建议使用不可修改的地图作为常量,否则它不能被视为常量。

有一个卢克提出的答案 ,它使用Java 8初始化一个地图,但恕我直言,它看起来难看难读。 我们可以创建一个地图条目流。 我们在java.util.AbstractMap已经有两个实现了SimpleEntry和SimpleImmutableEntry的Entry 。 对于这个例子,我们可以使用前者作为:

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

对于Java 9,我们也可以使用Tagir在他的回答中提到的使用Map.of。

我强烈建议静态块风格的“双大括号初始化”风格。

有人可能会评论说,他们不喜欢匿名的课程,开销,表现等

但是我更多考虑的是代码的可读性和可维护性。 在这个观点上,我站在一个双重大括号是一个更好的代码风格,而不是静态方法。

  1. 元素是嵌套和内联的。
  2. 这是更多的面向对象,而不是程序。
  3. 性能影响非常小,可以忽略。
  4. 更好的IDE大纲支持(而不是许多匿名静态块)
  5. 你保存了几行评论,使他们的关系。
  6. 防止来自异常和字节码优化器的未初始化对象的可能的元素泄露/实例引导。
  7. 不用担心静态块的执行顺序。

另外,你知道匿名类的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"} }); 

你正在创建的匿名类运作良好。 但是,您应该知道这是一个内部类,因此它将包含对周围类实例的引用。 所以你会发现你不能用它做某些事情(使用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); } } } 

我更喜欢使用静态初始化器来避免生成匿名类(这将没有更多的目的),所以我会列出使用静态初始化器进行初始化的技巧。 所有列出的解决方案/提示都是类型安全的。

注意:这个问题没有提到让地图变得不可修改的问题,所以我将把它留在外面,但是要知道,使用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); } 

这里的helper方法不可重用,因为它只能添加元素到myMap2 。 为了使其可重用,我们可以将映射本身作为辅助方法的一个参数,但是初始化代码不会更短。

第三个提示

第三个技巧是你可以使用填充功能创建一个可重用的builder-like助手类。 这真的是一个简单的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 } } 

当我不想(或不能)使用Guava的ImmutableMap.of() ,或者如果我需要一个可变的Map时,这是我的最爱:

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

您可以使用StickyMapMapEntry

 private static final Map<String, String> MAP = new StickyMap<>( new MapEntry<>("name", "Jeffrey"), new MapEntry<>("age", "35") ); 

我不喜欢静态初始化语法,我不相信匿名子类。 一般来说,我同意使用静态初始值设定项的所有缺点,以及使用前面答案中提到的匿名子类的所有缺点。 另一方面,在这些职位上提出的专业人士是不够的。 我更喜欢使用静态初始化方法:

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

由于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}

这比一次创建和填充地图要方便得多。

在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之外的任何东西
  • 它是类型安全的,容易适应不同类型的关键和价值。

那么…我喜欢枚举;)

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

第二种方法可以根据需要调用受保护的方法。 这可以用于初始化构建后不可变的类。

我喜欢匿名类的语法; 它只是少了一些代码。 但是,我发现的一个主要的问题是,您将无法通过远程序列化该对象。 你会得到一个异常,无法找到远程端的匿名类。

我做了一些不同的事情。 不是最好的,但它适用于我。 也许它可以“通用化”。

 private static final Object[][] ENTRIES = { {new Integer(1), "one"}, {new Integer(2), "two"}, }; private static final Map myMap = newMap(ENTRIES); private static Map newMap(Object[][] entries) { Map map = new HashMap(); for (int x = 0; x < entries.length; x++) { Object[] entry = entries[x]; map.put(entry[0], entry[1]); } return map; } 

现在Java 8已经出来了,这个问题值得重温。 我采取了刺戳 – 看起来也许你可以利用lambda表达式语法来得到一个相当不错的简洁(但类型安全)的地图文字语法,看起来像这样:

  Map<String,Object> myMap = hashMap( bob -> 5, TheGimp -> 8, incredibleKoolAid -> "James Taylor", heyArnold -> new Date() ); Map<String,Integer> typesafeMap = treeMap( a -> 5, bee -> 8, sea -> 13 deep -> 21 ); 

未经测试的示例代码在https://gist.github.com/galdosd/10823529会好奇别人的意见在这(这是温和的邪恶;…)

我喜欢使用静态初始化“技术”,当我有一个抽象类的具体实现已经定义了初始化构造函数,但没有默认的构造函数,但我希望我的子类有一个默认的构造函数。

例如:

 public abstract class Shape { public static final String COLOR_KEY = "color_key"; public static final String OPAQUE_KEY = "opaque_key"; private final String color; private final Boolean opaque; /** * Initializing constructor - note no default constructor. * * @param properties a collection of Shape properties */ public Shape(Map<String, Object> properties) { color = ((String) properties.getOrDefault(COLOR_KEY, "black")); opaque = (Boolean) properties.getOrDefault(OPAQUE_KEY, false); } /** * Color property accessor method. * * @return the color of this Shape */ public String getColor() { return color; } /** * Opaque property accessor method. * * @return true if this Shape is opaque, false otherwise */ public Boolean isOpaque() { return opaque; } } 

和我这个类的具体实现 – 但它需要/需要一个默认的构造函数:

 public class SquareShapeImpl extends Shape { private static final Map<String, Object> DEFAULT_PROPS = new HashMap<>(); static { DEFAULT_PROPS.put(Shape.COLOR_KEY, "yellow"); DEFAULT_PROPS.put(Shape.OPAQUE_KEY, false); } /** * Default constructor -- intializes this square to be a translucent yellow */ public SquareShapeImpl() { // the static initializer was useful here because the call to // this(...) must be the first statement in this constructor // ie, we can't be mucking around and creating a map here this(DEFAULT_PROPS); } /** * Initializing constructor -- create a Square with the given * collection of properties. * * @param props a collection of properties for this SquareShapeImpl */ public SquareShapeImpl(Map<String, Object> props) { super(props); } } 

then to use this default constructor, we simply do:

 public class StaticInitDemo { public static void main(String[] args) { // create a translucent, yellow square... Shape defaultSquare = new SquareShapeImpl(); // etc... } } 

I have not seen the approach I use (and have grown to like) posted in any answers, so here it is:

I don't like using static initializers because they are clunky, and I don't like anonymous classes because it is creating a new class for each instance.

instead, I prefer initialization that looks like this:

 map( entry("keyA", "val1"), entry("keyB", "val2"), entry("keyC", "val3") ); 

unfortunately, these methods are not part of the standard Java library, so you will need to create (or use) a utility library that defines the following methods:

  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) 

(you can use 'import static' to avoid needing to prefix the method's name)

I found it useful to provide similar static methods for the other collections (list, set, sortedSet, sortedMap, etc.)

Its not quite as nice as json object initialization, but it's a step in that direction, as far as readability is concerned.