¿Cómo puedo inicializar un mapa estático?

¿Cómo inicializaría un mapa estático en Java?

Método uno: inicializador estático
Método dos: inicializador de instancia (subclase anónima) o algún otro método?

¿Cuáles son los pros y los contras de cada uno?

Aquí hay un ejemplo que ilustra dos métodos:

import java.util.HashMap; import java.util.Map; public class Test { private static final Map myMap = new HashMap(); static { myMap.put(1, "one"); myMap.put(2, "two"); } private static final Map myMap2 = new HashMap(){ { put(1, "one"); put(2, "two"); } }; } 

El inicializador de instancia es solo azúcar sintáctico en este caso, ¿verdad? No veo por qué necesitas una clase anónima adicional solo para inicializar. Y no funcionará si la clase que se está creando es definitiva.

También puede crear un mapa inmutable utilizando un inicializador estático:

 public class Test { private static final Map myMap; static { Map aMap = ....; aMap.put(1, "one"); aMap.put(2, "two"); myMap = Collections.unmodifiableMap(aMap); } } 

Me gusta la forma de Guava de inicializar un mapa estático e inmutable:

 static final Map MY_MAP = ImmutableMap.of( 1, "one", 2, "two" ); 

Como puede ver, es muy conciso (debido a los convenientes métodos de fábrica en ImmutableMap ).

Si desea que el mapa tenga más de 5 entradas, ya no podrá usar ImmutableMap.of() . En su lugar, pruebe ImmutableMap.builder() en estas líneas:

 static final Map MY_MAP = ImmutableMap.builder() .put(1, "one") .put(2, "two") // ... .put(15, "fifteen") .build(); 

Para obtener más información sobre los beneficios de las utilidades de recolección inmutables de Guava, consulte ” Colecciones Inmutables Explicadas en Guava User Guide” .

(Un subconjunto de) Guava solía llamarse Google Collections . Si aún no está utilizando esta biblioteca en su proyecto Java, ¡recomiendo probarlo! La guayaba se ha convertido rápidamente en una de las librerías de terceros gratuitas más populares y útiles para Java, como lo acuerdan los demás usuarios de SO . (Si es nuevo en esto, hay algunos excelentes recursos de aprendizaje detrás de ese enlace).


Actualización (2015) : en cuanto a Java 8 , bueno, aún usaría el enfoque de Guava porque es mucho más limpio que cualquier otra cosa. Si no quieres la dependencia de Guava, considera un viejo método simple de init . El truco con matriz bidimensional y API Stream es bastante feo si me preguntas, y se pone más feo si necesitas crear un mapa cuyas claves y valores no sean del mismo tipo (como Map en la pregunta).

En cuanto al futuro de la guayaba en general, con respecto a Java 8, Louis Wasserman dijo esto en 2014, y [ actualización ] en 2016, se anunció que Guava 21 requerirá y soportará adecuadamente Java 8 .


Actualización (2016) : Tal como lo señala Tagir Valeev , Java 9 finalmente hará que esto sea más fácil usando nada más que JDK puro, al agregar métodos de fábrica de conveniencia para las colecciones:

 static final Map MY_MAP = Map.of( 1, "one", 2, "two" ); 

Yo usaría:

 public class Test { private static final Map MY_MAP = createMap(); private static Map createMap() { Map result = new HashMap(); result.put(1, "one"); result.put(2, "two"); return Collections.unmodifiableMap(result); } } 
  1. evita la clase anónima, que personalmente considero un mal estilo, y evita
  2. hace que la creación del mapa sea más explícita
  3. hace que el mapa no se pueda modificar
  4. como MY_MAP es constante, lo nombraría como constante

Java 5 proporciona esta syntax más compacta:

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

Una ventaja del segundo método es que puede envolverlo con Collections.unmodifiableMap() para garantizar que nada va a actualizar la colección más adelante:

 private static final Map CONSTANT_MAP = Collections.unmodifiableMap(new HashMap() {{ put(1, "one"); put(2, "two"); }}); // later on... CONSTANT_MAP.put(3, "three"); // going to throw an exception! 

Aquí hay un inicializador de mapas estáticos Java 8 de una línea:

 private static final Map 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])); 

Editar: para inicializar un Map como en la pregunta, necesitarías algo como esto:

 static final Map MY_MAP = Arrays.stream(new Object[][]{ {1, "one"}, {2, "two"}, }).collect(Collectors.toMap(kv -> (Integer) kv[0], kv -> (String) kv[1])); 

Editar (2): i_am_zero tiene una mejor versión de tipo mixto que usa una secuencia de new SimpleEntry<>(k, v) . Mira esa respuesta: https://stackoverflow.com/a/37384773/3950982

En Java 9:

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

Ver JEP 269 para más detalles. JDK 9 alcanzó disponibilidad general en septiembre de 2017.

Con Eclipse Collections (anteriormente GS Collections ), todo lo siguiente funcionará:

 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 MAP = Maps.mutable.with(1, "one", 2, "two"); private static final MutableMap MUTABLE_MAP = Maps.mutable.with(1, "one", 2, "two"); private static final MutableMap UNMODIFIABLE_MAP = Maps.mutable.with(1, "one", 2, "two").asUnmodifiable(); private static final MutableMap SYNCHRONIZED_MAP = Maps.mutable.with(1, "one", 2, "two").asSynchronized(); private static final ImmutableMap IMMUTABLE_MAP = Maps.mutable.with(1, "one", 2, "two").toImmutable(); private static final ImmutableMap IMMUTABLE_MAP2 = Maps.immutable.with(1, "one", 2, "two"); } 

También puede inicializar estáticamente mapas primitivos con colecciones de 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 MUTABLE_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two"); private static final MutableIntObjectMap UNMODIFIABLE_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two") .asUnmodifiable(); private static final MutableIntObjectMap SYNCHRONIZED_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two") .asSynchronized(); private static final ImmutableIntObjectMap IMMUTABLE_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two") .toImmutable(); private static final ImmutableIntObjectMap IMMUTABLE_INT_OBJ_MAP2 = IntObjectMaps.immutable.empty() .newWithKeyValue(1, "one") .newWithKeyValue(2, "two"); } 

Nota: soy un committer para las colecciones de Eclipse

Nunca crearía una subclase anónima en esta situación. Los inicializadores estáticos funcionan igual de bien, si desea que el mapa no se pueda modificar, por ejemplo:

 private static final Map MY_MAP; static { MaptempMap = new HashMap(); tempMap.put(1, "one"); tempMap.put(2, "two"); MY_MAP = Collections.unmodifiableMap(tempMap); } 

Tal vez sea interesante consultar Google Collections , por ejemplo, los videos que tienen en su página. Proporcionan varias formas de inicializar mapas y conjuntos, y proporcionan colecciones inmutables también.

Actualización: Esta biblioteca ahora se llama Guava .

Me gusta la clase anónima, porque es fácil de manejar:

 public static final Map< ?, ?> numbers = Collections.unmodifiableMap(new HashMap() { { put(1, "some value"); //rest of code here } }); 

Hay una respuesta propuesta por Luke que inicializa un mapa usando Java 8 pero en mi humilde opinión se ve feo y difícil de leer. Podemos crear una secuencia de entradas de mapas. Ya tenemos dos implementaciones de Entry en java.util.AbstractMap que son SimpleEntry y SimpleImmutableEntry . Para este ejemplo, podemos hacer uso de ex como:

 import java.util.AbstractMap.*; private static final Map 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)); 

Para Java 9 también podemos hacer uso de Map.of como lo sugiere Tagir en su respuesta aquí .

 public class Test { private static final Map myMap; static { Map aMap = ....; aMap.put(1, "one"); aMap.put(2, "two"); myMap = Collections.unmodifiableMap(aMap); } } 

Si declaramos más de una constante, ese código se escribirá en bloque estático y es difícil de mantener en el futuro. Entonces es mejor usar una clase anónima.

 public class Test { public static final Map numbers = Collections.unmodifiableMap(new HashMap(2, 1.0f){ { put(1, "one"); put(2, "two"); } }); } 

Y se sugiere usar una matriz no modificable para las constantes, de lo contrario no se puede tratar como constante.

Podría sugerir fuertemente el estilo de “inicialización de llave doble” sobre el estilo de bloque estático.

Alguien puede comentar que no les gusta la clase anónima, los gastos generales, el rendimiento, etc.

Pero lo que más considero es la legibilidad y mantenibilidad del código. En este punto de vista, tengo una doble llave es un mejor estilo de código en lugar de método estático.

  1. Los elementos están nesteds y en línea.
  2. Es más OO, no de procedimiento.
  3. el impacto en el rendimiento es realmente pequeño y podría ser ignorado.
  4. Mejor soporte de esquema IDE (en lugar de muchos bloques anónimos estáticos {})
  5. Has guardado algunas líneas de comentarios para traerles una relación.
  6. Evite la posible pérdida de elementos / derivación de instancias de objetos no inicializados de la excepción y el optimizador de códigos de bytes.
  7. No se preocupe por el orden de ejecución del bloque estático.

Además, si conoce el GC de la clase anónima, siempre puede convertirlo a un HashMap normal utilizando el new HashMap(Map map) .

Puedes hacer esto hasta que te enfrentas a otro problema. Si lo hace, debe usar otro estilo de encoding (por ejemplo, no estático, clase de fábrica) para ello.

Como siempre, apache-commons tiene el método adecuado MapUtils.putAll (Map, Object []) :

Por ejemplo, para crear un mapa de color:

 Map colorMap = MapUtils.putAll(new HashMap(), new String[][] { {"RED", "#FF0000"}, {"GREEN", "#00FF00"}, {"BLUE", "#0000FF"} }); 

Si desea un mapa no modificable, finalmente java 9 agregó un método of fábrica genial para la interfaz de Map . Se agrega un método similar a Set, List también.

Map unmodifiableMap = Map.of("key1", "value1", "key2", "value2");

La clase anónima que estás creando funciona bien. Sin embargo, debe tener en cuenta que esta es una clase interna y, como tal, contendrá una referencia a la instancia de clase circundante. Entonces descubrirá que no puede hacer ciertas cosas con él (usando XStream para uno). Obtendrás algunos errores muy extraños.

Habiendo dicho eso, siempre que sepa, este enfoque está bien. Lo uso la mayor parte del tiempo para inicializar todo tipo de colecciones de forma concisa.

EDITAR: señalado correctamente en los comentarios que esta es una clase estática. Obviamente no lo leí lo suficiente. Sin embargo, mis comentarios todavía se aplican a las clases internas anónimas.

Este es mi favorito cuando no quiero (o no puedo) usar ImmutableMap.of() Guava, o si necesito un Map mutable:

 public static  Map asMap(Object... keysAndValues) { return new LinkedHashMap() {{ for (int i = 0; i < keysAndValues.length - 1; i++) { put(keysAndValues[i].toString(), (A) keysAndValues[++i]); } }}; } 

Es muy compacto e ignora los valores parásitos (es decir, una clave final sin valor).

Uso:

 Map one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal"); Map two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2)); 

Si desea algo escueto y relativamente seguro, puede cambiar la verificación de tipos en tiempo de comstackción al tiempo de ejecución:

 static final Map map = MapUtils.unmodifiableMap( String.class, Integer.class, "cat", 4, "dog", 2, "frog", 17 ); 

Esta implementación debería detectar cualquier error:

 import java.util.HashMap; public abstract class MapUtils { private MapUtils() { } public static  HashMap unmodifiableMap( Class< ? extends K> keyClazz, Class< ? extends V> valClazz, Object...keyValues) { return Collections.unmodifiableMap(makeMap( keyClazz, valClazz, keyValues)); } public static  HashMap 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 result = new HashMap(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 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); } } } 

Prefiero usar un inicializador estático para evitar la generación de clases anónimas (que no tendrían ningún otro propósito), así que enumeraré las sugerencias inicializando con un inicializador estático. Todas las soluciones / sugerencias enumeradas son seguras para el tipo.

Nota: La pregunta no dice nada sobre hacer que el mapa no se pueda modificar, así que lo dejaré, pero sé que se puede hacer fácilmente con Collections.unmodifiableMap(map) .

Primer consejo

El primer consejo es que puedes hacer una referencia local al mapa y le das un nombre CORTO:

 private static final Map myMap = new HashMap<>(); static { final Map 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"); } 

Segundo consejo

El segundo consejo es que puedes crear un método de ayuda para agregar entradas; También puede hacer público este método de ayuda si lo desea:

 private static final Map 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); } 

El método de ayuda aquí no es reutilizable, porque solo puede agregar elementos a myMap2 . Para que sea reutilizable, podríamos hacer que el mapa sea un parámetro del método de ayuda, pero el código de inicialización no sería más corto.

Tercer consejo

El tercer consejo es que puedes crear una clase de ayudante similar a un constructor reutilizable con la función de poblado. Esta es realmente una clase de ayuda simple de 10 líneas que es segura para el tipo de letra:

 public class Test { private static final Map 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 { private final Map m; public B(Map m) { this.m = m; } public B p(K k, V v) { m.put(k, v); return this; // Return this for chaining } } 

Puede usar StickyMap y MapEntry de Cactoos :

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

No me gusta la syntax del inicializador estático y no estoy convencido de subclases anónimas. En general, estoy de acuerdo con todas las contras de usar Inicializadores Estáticos y todos los inconvenientes de usar subclases anónimas que se mencionaron en las respuestas previas. Por otro lado, los profesionales presentados en estos posts no son suficientes para mí. Prefiero usar el método de inicialización estático:

 public class MyClass { private static final Map myMap = prepareMap(); private static Map prepareMap() { Map hashMap = new HashMap<>(); hashMap.put(1, "one"); hashMap.put(2, "two"); return hashMap; } } 

Debido a que Java no es compatible con los literales del mapa, las instancias del mapa siempre se deben instanciar y llenar explícitamente.

Afortunadamente, es posible aproximar el comportamiento de los literales del mapa en Java utilizando métodos de fábrica .

Por ejemplo:

 public class LiteralMapFactory { // Creates a map from a list of entries @SafeVarargs public static  Map mapOf(Map.Entry... entries) { LinkedHashMap map = new LinkedHashMap<>(); for (Map.Entry entry : entries) { map.put(entry.getKey(), entry.getValue()); } return map; } // Creates a map entry public static  Map.Entry 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))); } } 

Salida:

{a = 1, b = 2, c = 3}

Es mucho más conveniente que crear y poblar el mapa de un elemento a la vez.

Con Java 8 he llegado a utilizar el siguiente patrón:

 private static final Map MAP = Stream.of( new AbstractMap.SimpleImmutableEntry<>("key1", 1), new AbstractMap.SimpleImmutableEntry<>("key2", 2) ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 

No es la rotonda más concisa y un poco más, pero

  • no requiere nada fuera de java.util
  • es seguro y acomoda fácilmente diferentes tipos de clave y valor.

Se piensa que su segundo enfoque (inicialización de Double Brace) es un anti patrón , por lo que me decantaría por el primer enfoque.

Otra forma fácil de inicializar un mapa estático es mediante el uso de esta función de utilidad:

 public static  Map mapOf(Object... keyValues) { Map 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 map1 = mapOf(1, "value1", 2, "value2"); Map map2 = mapOf("key1", "value1", "key2", "value2"); 

Nota: en Java 9 puedes usar Map.of

No he visto el enfoque que uso (y me gusta) publicado en las respuestas, así que aquí está:

No me gusta usar los inicializadores estáticos porque son torpes, y no me gustan las clases anónimas porque está creando una nueva clase para cada instancia.

en cambio, prefiero la inicialización que se ve así:

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

desafortunadamente, estos métodos no son parte de la biblioteca estándar de Java, por lo que deberá crear (o usar) una biblioteca de utilidad que defina los siguientes métodos:

  public static  Map map(Map.Entry... entries) public static  Map.Entry entry(K key, V val) 

(puede usar ‘importar estática’ para evitar tener que prefijar el nombre del método)

Me pareció útil proporcionar métodos estáticos similares para las otras colecciones (list, set, sortedSet, sortedMap, etc.)

No es tan agradable como la inicialización de objetos json, pero es un paso en esa dirección, en lo que respecta a la legibilidad.

JEP 269 proporciona algunos métodos de fábrica de conveniencia para Collections API. Estos métodos de fábrica no están en la versión actual de Java, que es 8, pero están planificados para la versión de Java 9.

Para Map hay dos métodos de fábrica: of y ofEntries . Al usar of , puede pasar pares de clave / valor alternativos. Por ejemplo, para crear un Map como {age: 27, major: cs} :

 Map info = Map.of("age", 27, "major", "cs"); 

Actualmente hay diez versiones sobrecargadas para, de modo que puede crear un mapa que contenga diez pares clave / valor. Si no le gusta esta limitación o alternar clave / valores, puede usar ofEntries :

 Map info = Map.ofEntries( Map.entry("age", 27), Map.entry("major", "cs") ); 

Ambos y ofEntries devolverán un Map inmutable, por lo que no puede cambiar sus elementos después de la construcción. Puede probar estas funciones utilizando JDK 9 Early Access .

Si solo necesita agregar un valor al mapa, puede usar Collections.singletonMap :

 Map map = Collections.singletonMap(key, value) 

Bueno … me gustan los enums;)

 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 MAP = Stream.of( values() ) .collect( Collectors.toMap( e -> e.value, e -> e.name ) ); } 

El segundo método podría invocar métodos protegidos si es necesario. Esto puede ser útil para inicializar clases que son inmutables después de la construcción.