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

1132

¿Cómo inicializarías una estática Mapen 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 los dos métodos:

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");
        }
    };
}
dogbane
fuente
2
Para inicializar un mapa en Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal
2
Por favor, nunca use la inicialización de doble llave : es un truco y una forma fácil de perder memoria y causar otros problemas.
dimo414
Java 9? Si las entradas cuentan <= 10 use Map.ofelse Map.ofEntries, consulte stackoverflow.com/a/37384773/1216775
akhil_mittal el

Respuestas:

1106

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

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

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);
    }
}
Variable miserable
fuente
10
Este es el idioma que he usado durante años y nunca he tenido a nadie que lo mire. También hago lo mismo para conjuntos y listas constantes no modificables.
jasonmp85
3
¿Cómo manejaría un HashMap <String, String> con una clave String? El objeto Map no me permite tener una clave de cadena, por lo que no puedo usar unmodifiableMap (). Supongo que lanzar a un HashMap también anularía el propósito. ¿Algunas ideas?
Lucas
30
@Luke Dudo seriamente que Android tenga tal limitación. No tiene ningún sentido. Una búsqueda rápida encontró esta pregunta aquí (y muchas otras) que parece implicar que puede usar una clave de cadena para un objeto de mapa en Android.
mluisbrown
11
Para que nadie más se moleste en investigar, puedo confirmar que no hay ningún problema con el uso de una clave de cadena para un objeto Map en Android.
Jordan
11
Jordan: ahora es un tema antiguo, pero sospecho que @Luke estaba tratando de usar una cadena como clave en un mapa que tenía un tipo de clave diferente, por ejemplo, Mapa <Entero, Cadena>.
Variable miserable
445

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

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

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

Si desea que el mapa tenga más de 5 entradas, ya no puede usarlo ImmutableMap.of(). En su lugar, intente ImmutableMap.builder()siguiendo estas líneas:

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

Para obtener más información sobre los beneficios de las utilidades de colección inmutables de Guava, consulte Colecciones inmutables explicadas en la Guía del usuario de Guava .

(Un subconjunto de) La guayaba solía llamarse Google Collections . Si no está utilizando esta biblioteca en su proyecto de Java, sin embargo, yo fuertemente recomiendo probarlo! La guayaba se ha convertido rápidamente en una de las librerías gratuitas de terceros más populares y útiles para Java, como coinciden otros usuarios de SO . (Si eres nuevo en él, hay algunos excelentes recursos de aprendizaje detrás de ese enlace).


Actualización (2015) : En cuanto a Java 8 , bueno, todavía usaría el enfoque de Guava porque es mucho más limpio que cualquier otra cosa. Si no desea la dependencia de Guava, considere un método init simple y antiguo . El hack con matriz bidimensional y Stream API es bastante feo si me preguntas, y se vuelve más feo si necesitas crear un mapa cuyas claves y valores no sean del mismo tipo (como Map<Integer, String>en la pregunta).

En cuanto al futuro de Guava 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 admitirá Java 8 de manera adecuada .


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

static final Map<Integer, String> MY_MAP = Map.of(
    1, "one", 
    2, "two"
);
Jonik
fuente
21
Parece que nuestros colegas administradores de SO han eliminado la venerable pregunta "Bibliotecas Java gratuitas de terceros más útiles" a la que me he vinculado. :( Malditos sean.
Jonik
2
Estoy de acuerdo, esta es la mejor manera de inicializar un mapa constante. No solo es más legible, sino también porque Collections.unmodifiableMap devuelve una vista de solo lectura del mapa subyacente (que aún se puede modificar).
crunchdog
11
Ahora puedo ver las preguntas eliminadas (con 10k + rep), así que aquí hay una copia de 'Las bibliotecas Java gratuitas de terceros más útiles' . Es solo la primera página, pero al menos puede encontrar los recursos de guayaba mencionados anteriormente.
Jonik
2
Realmente prefiero este enfoque, aunque es beneficioso saber cómo hacerlo sin dependencias adicionales.
Wrench
2
JEP 186 todavía no está cerrado, por lo que puede introducir nuevas características relacionadas con los literales de colección
cybersoft
182

Yo usaría:

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);
    }
}
  1. evita una clase anónima, que personalmente considero un mal estilo, y evito
  2. hace que la creación del mapa sea más explícita
  3. hace que el mapa sea inmodificable
  4. como MY_MAP es constante, lo nombraría como constante
Peter Štibraný
fuente
3
De las opciones JDK puras (sin libs), me gusta más, porque la definición del mapa está claramente vinculada a su inicialización. También se acordó nombrar constantemente.
Jonik
Nunca se me ocurrió que pudieras hacer esto.
romulusnr
181

Java 5 proporciona esta sintaxis más compacta:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};
Chris Noe
fuente
46
Esa técnica se llama inicialización de doble paréntesis: stackoverflow.com/questions/1372113/… No es una sintaxis especial de Java 5, es solo un truco con una clase anónima con un inicializador de instancia.
Jesper
13
Pregunta rápida sobre la inicialización de doble paréntesis: Al hacer esto, Eclipse emite una Advertencia sobre una ID de serie faltante. Por un lado, no veo por qué sería necesaria una ID de serie en este caso específico, pero por otro lado, generalmente no me gusta suprimir las advertencias. ¿Qué piensas sobre esto?
nbarraille
8
@nbarraille Eso es porque HashMap implements Serializable. Como en realidad crea una subclase de HashMap usando este "truco", está creando implícitamente una clase Serializable. Y para esto, debe proporcionar un número de serie.
nadie
55
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
Mark Jeronimus
3
@MarkJeronimus: el uso sugerido es un contexto estático. El rendimiento puede ser peor, pero no notablemente cuando se trata de un número presumiblemente pequeño de mapas estáticamente definidos. HashMap.equalsse define AbstractMapy funciona en cualquier subclase de Map, por lo que no es una preocupación aquí Lo del operador de diamantes es molesto, pero como se mencionó ahora se ha resuelto.
Jules
95

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

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!
Programador ilegal
fuente
3
¿No puede hacer esto fácilmente en el primer método moviendo el nuevo operador al bloque estático {} y envolviéndolo?
Patrick
2
De todos modos, movería la llamada del constructor a la estática inicializada. Cualquier otra cosa parece extraña.
Tom Hawtin - tackline
2
¿Alguna idea de qué rendimiento puede haber al utilizar una clase anónima en lugar de una clase concreta?
Kip
62

Aquí hay un inicializador de mapa estático de una línea 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]));

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

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

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

Luke Hutchison
fuente
77
Me tomé la libertad de agregar una versión que es equivalente a la pregunta y otras respuestas: iniciar un mapa cuyas claves y valores son de diferente tipo (por String[][]lo que no será Object[][]necesario). En mi humilde opinión, este enfoque es feo (aún más con los moldes) y difícil de recordar; No lo usaría yo mismo.
Jonik
57

Map.of en Java 9+

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

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

Tagir Valeev
fuente
77
O si desea más de 10 pares clave-valor, puede usarMap.ofEntries
ZhekaKozlov
8
Esto está limpio y todo, hasta que te des cuenta de cómo se implementó
mediados del
Ugh, eso es tan triste: parece que solo admite 10 entradas, después de lo cual debe usar ofEntries. Cojo.
Somaiah Kumbera
2
La limpieza de la implementación en el JDK no debería importar mientras funcione y satisfaga el contrato. Al igual que cualquier cuadro negro, detalles de implementación siempre se pueden fijar en el futuro si realmente se necesita ...
vikingsteve
@mid Esa es la única forma segura de escribir en Java.
Luke Hutchison el
44

Java 9

Podemos usar Map.ofEntries, llamando Map.entry( k , v )para crear cada entrada.

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

También podemos usar Map.ofcomo lo sugiere Tagir en su respuesta aquí, pero no podemos tener más de 10 entradas usando Map.of.

Java 8 (solución ordenada)

Podemos crear un flujo de entradas de mapa. Ya tenemos dos implementaciones de Entryen java.util.AbstractMapque son SimpleEntry y SimpleImmutableEntry . Para este ejemplo podemos hacer uso del anterior como:

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));
akhil_mittal
fuente
2
El new SimpleEntry<>()camino es mucho menos legible que estático put(): /
Danon
32

Con Eclipse Collections , funcionará todo lo siguiente:

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

También puede inicializar estáticamente mapas primitivos con 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");
} 

Nota: Soy un committer para Eclipse Collections

Donald Raab
fuente
1
Realmente deseo que Eclipse Collections sea la biblioteca de colecciones predeterminada para Java. Lo disfruto mucho más que Guava + JCL.
Kenny Cason
29

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

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);
}
eljenso
fuente
1
¿En qué situación usarías una subclase anónima para inicializar un hashmap?
dogbane
66
Nunca inicializar una colección.
eljenso
¿Podría explicar por qué usar un inicializador estático es una mejor opción que crear una subclase anónima?
leba-lev
3
@rookie Hay varias razones dadas en otras respuestas que favorecen el inicio estático. El objetivo aquí es inicializar, entonces, ¿por qué incorporar la subclase, excepto tal vez para guardar algunas pulsaciones de teclas? (Si desea ahorrar en las pulsaciones de teclas, Java definitivamente no es una buena opción como lenguaje de programación). Una regla general que uso cuando programo en Java es: subclasificar lo menos posible (y nunca cuando puede evitarse razonablemente).
eljenso
@eljenso: la razón por la que generalmente prefiero la sintaxis de la subclase para esto es que pone la inicialización en línea, donde pertenece . Una segunda mejor opción es llamar a un método estático que devuelve el mapa inicializado. Pero me temo que miraré su código y tendré que pasar unos segundos averiguando de dónde proviene MY_MAP, y ese es el momento que no quiero perder. Cualquier mejora en la legibilidad es una ventaja, y las consecuencias de rendimiento son mínimas, por lo que me parece la mejor opción.
Jules
18

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

Actualización: esta biblioteca ahora se llama Guava .

Kaarel
fuente
17

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

public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
    {
        put(1, "some value");
                    //rest of code here
    }
});
Shushant
fuente
12
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);
    }
}

Si declaramos más de una constante, ese código se escribirá en bloque estático y eso es difícil de mantener en el futuro. Por lo tanto, 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 utilizar un mapa no modificable para constantes; de lo contrario, no se puede tratar como constante.

Leninkumar Koppoju
fuente
10

Podría sugerir fuertemente el estilo de "inicialización de doble paréntesis" 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 facilidad de mantenimiento del código. En este punto de vista, creo que una doble llave es un mejor estilo de código en lugar de un método estático.

  1. Los elementos están anidados y en línea.
  2. Es más OO, no procesal.
  3. El impacto en el rendimiento es realmente pequeño y podría ignorarse.
  4. Mejor soporte de esquema IDE (en lugar de muchos bloques estáticos {} anónimos)
  5. Has guardado algunas líneas de comentarios para relacionarlos.
  6. Evite la posible fuga de elementos / derivación de instancia del objeto no inicializado desde la excepción y el optimizador de bytecode
  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 en un HashMap normal mediante el uso new HashMap(Map map).

Puede hacer esto hasta que enfrente otro problema. Si lo hace, debe usar otro estilo de codificación completo (por ejemplo, no estático, clase de fábrica) para ello.

Dennis C
fuente
8

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

Por ejemplo, para crear un mapa de colores:

Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
     {"RED", "#FF0000"},
     {"GREEN", "#00FF00"},
     {"BLUE", "#0000FF"}
 });
agad
fuente
Incluyo Apache Commons en todas las compilaciones, por lo que, en la desafortunada ausencia de un método Arrays.asMap( ... )en Java simple, creo que esta es la mejor solución. Reinventar la rueda suele ser una tontería. Un inconveniente muy leve es que con los genéricos necesitará una conversión sin control.
Mike roedor
@mikerodent 4.1 versión genérica es: public static <K, V> Mapa <K, V> putAll (mapa final <K, V> mapa, objeto final [] array)
agad
Tx ... sí, estoy usando 4.1 pero todavía tengo que hacerlo SuppressWarnings( unchecked )en Eclipse con una línea comoMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
mike rodent
@mikerodent ¿no es por Objeto [] [] ? Ver ropa interior actualizada: no tengo ninguna advertencia en Eclipse.
2016
¡Qué extraño ... incluso cuando voy String[][]recibo la "advertencia"! Y, por supuesto, eso solo funciona si tu Ky Veres de la misma clase. ¿Supongo que no ha (comprensiblemente) configurado "conversión no verificada" en "Ignorar" en su configuración de Eclipse?
Mike roedor
7

Este es mi favorito cuando no quiero (o no puedo) usar Guava's ImmutableMap.of(), o si necesito un mutable 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]);
        }
    }};
}

Es muy compacto e ignora los valores perdidos (es decir, una clave final sin un valor).

Uso:

Map<String, String> one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal");
Map<String, Object> two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2));
neu242
fuente
7

Si desea un mapa no modificable, finalmente Java 9 agregó un método de fábrica genial ofpara la Mapinterfaz. Se agrega un método similar a Set, List también.

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

Bharanidharan K
fuente
6

Prefiero usar un inicializador estático para evitar generar clases anónimas (que no tendrían ningún otro propósito), por lo que enumeraré sugerencias para inicializar con un inicializador estático. Todas las soluciones / consejos enumerados son de tipo seguro.

Nota: La pregunta no dice nada acerca de hacer que el mapa no se pueda modificar, por lo que lo dejaré fuera, pero sé que se puede hacer fácilmente Collections.unmodifiableMap(map).

Primer consejo

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

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

Segundo consejo

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

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

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

Tercer consejo

El tercer consejo es que puede crear una clase de ayuda reutilizable tipo constructor con la funcionalidad de completar. Esta es realmente una clase auxiliar simple de 10 líneas que es de tipo seguro:

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
    }
}
icza
fuente
5

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 lo sepas, este enfoque está bien. Lo uso la mayor parte del tiempo para inicializar todo tipo de colecciones de manera concisa.

EDITAR: señaló correctamente en los comentarios que esta es una clase estática. Obviamente no leí esto lo suficientemente de cerca. Sin embargo mis comentarios no se siguen aplicando a las clases internas anónimas.

Brian Agnew
fuente
3
En este caso particular es estático, por lo que no hay instancia externa.
Tom Hawtin - tackline
Podría decirse que XStream no debería intentar serializar cosas como esta (es estático. ¿Por qué necesitaría serializar una variable estática?)
jasonmp85
5

Si desea algo conciso y relativamente seguro, puede cambiar la verificación del tipo de tiempo de compilación al tiempo de ejecución:

static final Map<String, Integer> 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 <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);
        }
    }
}
Philip Guin
fuente
4

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

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

No es lo más conciso y un poco indirecto, pero

  • no requiere nada fuera de java.util
  • es seguro y admite fácilmente diferentes tipos de clave y valor.
zrvan
fuente
si es necesario, se puede usar la toMapfirma, incluido un proveedor de mapas, para especificar el tipo de mapa.
zrvan
4

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

Map<K, V> map = Collections.singletonMap(key, value)
Estromas
fuente
4

Puede usar StickyMapy MapEntrydesde Cactoos :

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

Se cree que su segundo enfoque (inicialización de doble paréntesis) es un antipatrón , por lo que elegiría 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 <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");

Nota: en Java 9puedes usar Map.of

R. Oosterholt
fuente
3

No me gusta la sintaxis del inicializador estático y no estoy convencido de las subclases anónimas. En general, estoy de acuerdo con todas las desventajas de usar inicializadores estáticos y todas las desventajas de usar subclases anónimas que se mencionaron en respuestas anteriores. Por otro lado, los profesionales presentados en estas publicaciones no son suficientes para mí. Prefiero usar el método de inicialización estática:

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;
    }
}
Stanisław Borowy
fuente
3

No he visto el enfoque que uso (y me ha gustado) publicado en ninguna respuesta, así que aquí está:

No me gusta usar 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 una inicialización que se vea 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 utilidades que defina los siguientes métodos:

 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)

(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 bueno como la inicialización de objetos json, pero es un paso en esa dirección, en lo que respecta a la legibilidad.

josh
fuente
3

Debido a que Java no admite literales de mapas, las instancias de mapas siempre se deben instanciar y completar explícitamente.

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

Por ejemplo:

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

Salida:

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

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

nazar_art
fuente
2

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

Porque Maphay dos métodos de fábrica: ofy ofEntries. Usando of, puede pasar pares clave / valor alternativos. Por ejemplo, para crear un me Mapgusta {age: 27, major: cs}:

Map<String, Object> info = Map.of("age", 27, "major", "cs");

Actualmente hay diez versiones sobrecargadas of, por lo que puede crear un mapa que contenga diez pares clave / valor. Si no le gusta esta limitación o la alternancia de clave / valores, puede usar ofEntries:

Map<String, Object> info = Map.ofEntries(
                Map.entry("age", 27),
                Map.entry("major", "cs")
);

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

Ali Dehghani
fuente
2

Bueno ... me gustan las enumeraciones;)

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 ) );
}
jglatre
fuente
2

He leído las respuestas y decidí escribir mi propio creador de mapas. Siéntase libre de copiar y pegar y disfrutar.

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

EDITAR: Últimamente, sigo encontrando métodos estáticos públicos con ofbastante frecuencia y me gusta un poco. Lo agregué al código e hice privado al constructor, cambiando así al patrón de método de fábrica estático.

EDIT2: Incluso más recientemente, ya no me gusta el método estático llamado of, ya que se ve bastante mal cuando uso importaciones estáticas. En su mapOflugar, le cambié el nombre, lo que lo hace más adecuado para las importaciones estáticas.

Vlasec
fuente