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




entre diferencia (25)

Puedes usar StickyMap y MapEntry de Cactoos :

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

¿Cómo inicializarías un Map 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 los dos métodos:

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

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

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


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

Por ejemplo, para crear un mapa de color:

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

Me gusta la forma Guava 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 métodos de fábrica convenientes en ImmutableMap ).

Si desea que el mapa tenga más de 5 entradas, ya no puede usar 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 recopilación inmutables de Guava, consulte Colecciones inmutables explicadas en la Guía del usuario de Guava .

(Un subconjunto de) Guayaba solía llamarse Google Collections . Si aún no está utilizando esta biblioteca en su proyecto Java, ¡ le 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, según coinciden otros usuarios de SO . (Si eres nuevo en esto, hay algunos recursos de aprendizaje excelentes 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 la guayaba, considere un método de inicio antiguo simple . El hack con matriz bidimensional y Stream API es bastante feo si me lo 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 apoyará adecuadamente a Java 8 .

Actualización (2016) : Como señala Tagir Valeev , Java 9 finalmente lo hará todo para que no use nada más que JDK puro, agregando métodos de fábrica de conveniencia para las colecciones:

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

Aquí está mi favorito cuando no quiero (o no puedo) usar ImmutableMap.of() Guava, o si necesito un Map mutable:

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

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

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

No he visto el enfoque que utilizo (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 su lugar, prefiero la inicialización que se ve así:

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

desafortunadamente, estos métodos no forman 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 <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 agradable como la inicialización del objeto json, pero es un paso en esa dirección, en lo que respecta a la legibilidad.


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://.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> of(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 el método estático público con ofbastante frecuencia y me gusta. Lo agregué al código e hice que el constructor fuera privado, cambiando así al patrón de método estático de fábrica.


Se cree que su segundo enfoque (inicialización de doble refuerzo) es un patrón anti , por lo que me gustaría ir para 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 9 puedes usar Map.of


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<Integer, String>();
        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 evito
  2. Hace más explícita la creación de mapas.
  3. hace que el mapa no sea modificable
  4. como MY_MAP es constante, lo llamaría constante

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. Así que es mejor usar la 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 las constantes, de lo contrario no se puede tratar como constante.


Debido a que Java no es compatible con los literales de mapas, las instancias de mapas siempre deben crearse y crear instancias explícitas.

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 poblar el mapa un elemento a la vez.


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

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

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

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

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

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


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<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 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<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 comendador de Eclipse Collections.


El inicializador de instancia es solo azúcar sintáctica 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 crea es definitiva.

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

No me gusta la sintaxis del inicializador estático y no estoy convencido de las subclases anónimas. En general, estoy de acuerdo con todos los inconvenientes de usar inicializadores estáticos y todos los contras de usar subclases anónimas que se mencionaron en las 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;
    }
}

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

Podría sugerir fuertemente el estilo de "inicialización de doble refuerzo" sobre el estilo de bloque estático.

Alguien puede comentar que no les gustan las clases anónimas, 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, sostengo que un doble corchete es un mejor estilo de código en lugar del 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 muy pequeño y podría ignorarse.
  4. Mejor soporte de esquema IDE (en lugar de muchos bloques estáticos {} anónimos)
  5. Guardaste algunas líneas de comentario para traerles relación.
  6. Evitar posibles pérdidas de elementos / derivaciones de instancias de objetos no inicializados desde la excepción y el optimizador de bytecode.
  7. No te preocupes 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 usando un new HashMap(Map map) .

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


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 de inicialización 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 sea modificable, así que lo omitiré, pero sepa que puede hacerse 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<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 de ayuda 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 auxiliar aquí no es reutilizable, ya que solo puede agregar elementos a myMap2 . Para hacerlo reutilizable, podríamos hacer que el mapa en sí sea 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 ayudante similar a un constructor reutilizable con la funcionalidad de llenado. Esta es realmente una clase de ayudante 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
    }
}

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ó la disponibilidad general en septiembre de 2017.


Tal vez sea interesante revisar Google Collections , 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 .


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

Con Java 8 he venido 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 el más conciso y un poco rotundo, pero

  • no requiere nada fuera de java.util
  • es seguro para el tipo y se adapta fácilmente a diferentes tipos por clave y valor.

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

Si la velocidad es una preocupación, ¿por qué no dividir el conjunto de entradas y sus valores más comúnmente usados ​​en una tabla de búsqueda y luego hacer cualquier algoritmo mágico optimizado que se haya creado para los casos excepcionales?







java dictionary collections initialization idiomatic