¿Cómo inicializar directamente un HashMap (de forma literal)?

1095

¿Hay alguna forma de inicializar un Java HashMap como este ?:

Map<String,String> test = 
    new HashMap<String, String>{"test":"test","test":"test"};

¿Cuál sería la sintaxis correcta? No he encontrado nada al respecto. es posible? Estoy buscando la forma más corta / más rápida de poner algunos valores "finales / estáticos" en un mapa que nunca cambian y que se conocen de antemano al crear el Mapa.

jens
fuente
Muy relacionado: stackoverflow.com/questions/507602/… (Ambas preguntas tratan sobre la inicialización de un mapa constante con valores estáticos y finales).
Jonik
En Java 9: techiedelight.com/initialize-map-java9
Kamil Tomasz Jarmusik
Si usa apache.commons.collections, puede usar commons.apache.org/proper/commons-collections/javadocs/…
ax.

Respuestas:

1347

Todas las versiones

En caso de que necesite una sola entrada: la hay Collections.singletonMap("key", "value").

Para Java Versión 9 o superior:

Sí, esto es posible ahora. En Java 9 se han agregado un par de métodos de fábrica que simplifican la creación de mapas:

// this works for up to 10 elements:
Map<String, String> test1 = Map.of(
    "a", "b",
    "c", "d"
);

// this works for any number of elements:
import static java.util.Map.entry;    
Map<String, String> test2 = Map.ofEntries(
    entry("a", "b"),
    entry("c", "d")
);

En el ejemplo anterior, tanto testy test2será el mismo, sólo que con diferentes formas de expresar el Mapa. El Map.ofmétodo se define para hasta diez elementos en el mapa, mientras que el Map.ofEntriesmétodo no tendrá dicho límite.

Tenga en cuenta que en este caso el mapa resultante será un mapa inmutable. Si desea que el mapa sea mutable, puede copiarlo nuevamente, por ejemplo, usandomutableMap = new HashMap<>(Map.of("a", "b"));

(Ver también JEP 269 y el Javadoc )

Para hasta Java Versión 8:

No, deberá agregar todos los elementos manualmente. Puede usar un inicializador en una subclase anónima para acortar un poco la sintaxis:

Map<String, String> myMap = new HashMap<String, String>() {{
        put("a", "b");
        put("c", "d");
    }};

Sin embargo, la subclase anónima podría introducir comportamientos no deseados en algunos casos. Esto incluye, por ejemplo:

  • Genera una clase adicional que aumenta el consumo de memoria, el consumo de espacio en disco y el tiempo de inicio
  • En el caso de un método no estático: contiene una referencia al objeto sobre el que se invocó el método de creación. Eso significa que el objeto de la clase externa no se puede recolectar basura mientras el objeto de mapa creado todavía se hace referencia, bloqueando así memoria adicional

El uso de una función para la inicialización también le permitirá generar un mapa en un inicializador, pero evita los efectos secundarios desagradables:

Map<String, String> myMap = createMap();

private static Map<String, String> createMap() {
    Map<String,String> myMap = new HashMap<String,String>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    return myMap;
}
yanqui
fuente
3
Esto no funcionará si desea inicializar los elementos en una función ...
Michael
99
@ Michael: Bueno, sí, si quieres usar una función, entonces no puedes usar una no función. ¿Pero por qué quieres?
Yankee
66
y para los casos en que se necesita un mapa con una sola entrada que hay Collections.singletonMap():)
Skwisgaar
3
Ahora que se ha lanzado Java 9 estable, prefiero este enlace para Javadoc . ¡Y +1 porque una dependencia menos!
Franklin Yu
3
¿Dónde está entrydocumentado Java 9 ?
nobar
1030

Esta es una forma.

HashMap<String, String> h = new HashMap<String, String>() {{
    put("a","b");
}};

Sin embargo, debe tener cuidado y asegurarse de comprender el código anterior (crea una nueva clase que hereda de HashMap). Por lo tanto, debería leer más aquí: http://www.c2.com/cgi/wiki?DoubleBraceInitialization , o simplemente usar Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
gregory561
fuente
72
Funciona pero es feo y tiene efectos secundarios invisibles que el usuario debe comprender antes de hacerlo, por ejemplo, generar una clase anónima completa en el acto.
jprete
96
Sí, así escribí sobre tener cuidado y le di un enlace a la descripción.
gregory561
66
Gran enlace La referencia en ese enlace a GreencoddsTenthRuleOfProgramming vale la pena leer.
michaelok
19
¿puede agregar "como ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" ya que el método "de" está limitado a 5 pares como máximo?
kommradHomer
342

Si usted permite bibliotecas 3 ª parte, puede utilizar la guayaba 's ImmutableMap para lograr literal similar a la brevedad:

Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");

Esto funciona para hasta 5 pares clave / valor , de lo contrario puede usar su generador :

Map<String, String> test = ImmutableMap.<String, String>builder()
    .put("k1", "v1")
    .put("k2", "v2")
    ...
    .build();


  • tenga en cuenta que la implementación de ImmutableMap de Guava difiere de la implementación de HashMap de Java (más notablemente es inmutable y no permite claves / valores nulos)
  • para obtener más información, consulte el artículo de la guía del usuario de Guava sobre sus tipos de colecciones inmutables
Jens Hoffmann
fuente
26
Además, la guayaba tiene ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius
17
ImmutableMap no es lo mismo que un HashMap, ya que fallará en valores nulos, mientras que el mapa HashMap no lo hará.
Gewthen
2
Solo para ayudar a otros que puedan enfrentar este problema. Debe escribir el generador para convertirlo en Map <String, String>, de esta manera: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Thiago
esto es genial Jens!
gaurav
105

No hay una forma directa de hacer esto: Java no tiene literales de Map (todavía, creo que fueron propuestos para Java 8).

A algunas personas les gusta esto:

Map<String,String> test = new HashMap<String, String>(){{
       put("test","test"); put("test","test");}};

Esto crea una subclase anónima de HashMap, cuyo inicializador de instancia coloca estos valores. (Por cierto, un mapa no puede contener el doble del mismo valor, su segundo puesto sobrescribirá el primero. Usaré diferentes valores para los siguientes ejemplos).

La forma normal sería esta (para una variable local):

Map<String,String> test = new HashMap<String, String>();
test.put("test","test");
test.put("test1","test2");

Si su testmapa es una variable de instancia, coloque la inicialización en un constructor o inicializador de instancia:

Map<String,String> test = new HashMap<String, String>();
{
    test.put("test","test");
    test.put("test1","test2");
}

Si su testmapa es una variable de clase, coloque la inicialización en un inicializador estático:

static Map<String,String> test = new HashMap<String, String>();
static {
    test.put("test","test");
    test.put("test1","test2");
}

Si desea que su mapa nunca cambie, debe después de la inicialización ajustar su mapa Collections.unmodifiableMap(...). También puede hacer esto en un inicializador estático:

static Map<String,String> test;
{
    Map<String,String> temp = new HashMap<String, String>();
    temp.put("test","test");
    temp.put("test1","test2");
    test = Collections.unmodifiableMap(temp);
}

(No estoy seguro de si ahora puede hacer la testfinal ... pruébelo e informe aquí).

Paŭlo Ebermann
fuente
61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};
Rana peluda
fuente
Simple y al grano. Creo que esto con una sección extendida de comentarios sería la mejor respuesta.
ooolala
15
Sin embargo, hay implicaciones de memoria que deben tenerse en cuenta. blog.jooq.org/2014/12/08/…
Amalgovinus
1
@Amalgovinus Básicamente, al crear una nueva subclase, está codificando los argumentos de tipo de HashMapesta subclase. Esto solo puede funcionar si realmente los proporciona. (Con un nuevo HashMap (vacío), los argumentos de tipo no son relevantes.)
Paŭlo Ebermann
1
Me gusta su limpieza, pero crea una clase anónima innecesaria y tiene los problemas descritos aquí: c2.com/cgi/wiki?DoubleBraceInitialization
udachny
1
@hello_its_me: porque es lo mismo que stackoverflow.com/a/6802512/1386911 respuesta, solo el formato es diferente. Y en este caso, este formato extendido no tiene un valor adicional además del formato compacto para facilitar la lectura.
Daniel Hári
44

Una alternativa, usando clases y varargs simples de Java 7: cree una clase HashMapBuildercon este método:

public static HashMap<String, String> build(String... data){
    HashMap<String, String> result = new HashMap<String, String>();

    if(data.length % 2 != 0) 
        throw new IllegalArgumentException("Odd number of arguments");      

    String key = null;
    Integer step = -1;

    for(String value : data){
        step++;
        switch(step % 2){
        case 0: 
            if(value == null)
                throw new IllegalArgumentException("Null key value"); 
            key = value;
            continue;
        case 1:             
            result.put(key, value);
            break;
        }
    }

    return result;
}

Use el método de esta manera:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
Aerthel
fuente
Escribí una respuesta inspirada en la suya: stackoverflow.com/questions/507602/…
GeroldBroser reincorpora a Mónica el
1
Otra solución con Apache Utils que nunca se menciona pero es legible, utilizando versiones anteriores de Java: MapUtils.putAll (nuevo HashMap <String, String> (), nuevo Object [] {"Mi clave", "mi valor", ...
Rolintocour
4

tl; dr

Use Map.of…métodos en Java 9 y posteriores.

Map< String , String > animalSounds =
    Map.of(
        "dog"  , "bark" ,   // key , value
        "cat"  , "meow" ,   // key , value
        "bird" , "chirp"    // key , value
    )
;

Map.of

Java 9 agregó una serie de Map.ofmétodos estáticos para hacer exactamente lo que desea: crear una instancia de inmutable Mapusando sintaxis literal .

El mapa (una colección de entradas) es inmutable, por lo que no puede agregar o quitar entradas después de crear instancias. Además, la clave y el valor de cada entrada son inmutables, no se pueden cambiar. Ver el Javadoc para ver otras reglas, como no se permiten NULL, no se permiten claves duplicadas y el orden de iteración de las asignaciones es arbitrario.

Veamos estos métodos, utilizando algunos datos de muestra para un mapa del día de la semana a una persona que esperamos que trabaje ese día.

Person alice = new Person( "Alice" );
Person bob = new Person( "Bob" );
Person carol = new Person( "Carol" );

Map.of()

Map.ofcrea un vacio Map. Inmodificable, por lo que no puede agregar entradas. Aquí hay un ejemplo de dicho mapa, vacío sin entradas.

Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)Hay varios métodos que toman de 1 a 10 pares clave-valor. Aquí hay un ejemplo de dos entradas.

Map < DayOfWeek, Person > weekendWorker = 
        Map.of( 
            DayOfWeek.SATURDAY , alice ,     // key , value
            DayOfWeek.SUNDAY , bob           // key , value
        )
;

weekendWorker.toString (): {DOMINGO = Persona {nombre = 'Bob'}, SÁBADO = Persona {nombre = 'Alicia'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )toma cualquier número de objetos que implementan la Map.Entryinterfaz. Java agrupa dos clases que implementan esa interfaz, una mutable, la otra inmutable: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Pero no necesitamos especificar una clase concreta. Simplemente necesitamos llamarMap.entry( k , v ) método, pasar nuestra clave y nuestro valor, y recuperamos un objeto de una Map.Entryinterfaz de implementación de alguna clase .

Map < DayOfWeek, Person > weekdayWorker = Map.ofEntries(
        Map.entry( DayOfWeek.MONDAY , alice ) ,            // Call to `Map.entry` method returns an object implementing `Map.Entry`. 
        Map.entry( DayOfWeek.TUESDAY , bob ) ,
        Map.entry( DayOfWeek.WEDNESDAY , bob ) ,
        Map.entry( DayOfWeek.THURSDAY , carol ) ,
        Map.entry( DayOfWeek.FRIDAY , carol )
);

día laborable.toString (): {MIÉRCOLES = Persona {nombre = 'Bob'}, MARTES = Persona {nombre = 'Bob'}, JUEVES = Persona {nombre = 'Carol'}, VIERNES = Persona {nombre = 'Carol'} , LUNES = Persona {nombre = 'Alicia'}}

Map.copyOf

Java 10 agregó el método Map.copyOf. Pase un mapa existente, recupere una copia inmutable de ese mapa.

Notas

Tenga en cuenta que el orden de los mapas iterador produce a través de Map.ofson no garantizado. Las entradas tienen un orden arbitrario. No escriba código basado en el orden visto, ya que la documentación advierte que el pedido está sujeto a cambios.

Tenga en cuenta que todos estos Map.of…métodos devuelven un Mapde una clase no especificada . La clase concreta subyacente puede incluso variar de una versión de Java a otra. Este anonimato permite a Java elegir entre varias implementaciones, lo que mejor se adapte a sus datos particulares. Por ejemplo, si sus claves provienen de una enumeración , Java podría usar una EnumMapdebajo de las cubiertas.

Albahaca Bourque
fuente
1

Posiblemente podría hacer su propio Map.ofmétodo (que solo está disponible en Java 9 y versiones posteriores) fácilmente de 2 maneras fáciles

Hazlo con una cantidad establecida de parámetros

Ejemplo

public <K,V> Map<K,V> mapOf(K k1, V v1, K k2, V v2 /* perhaps more parameters */) {
    return new HashMap<K, V>() {{
      put(k1, v1);
      put(k2,  v2);
      // etc...
    }};
}

Hazlo usando una lista

También puede hacer esto usando una lista, en lugar de crear muchos métodos para un determinado conjunto de parámetros.

Ejemplo

public <K, V> Map<K, V> mapOf(List<K> keys, List<V> values) {
   if(keys.size() != values.size()) {
        throw new IndexOutOfBoundsException("amount of keys and values is not equal");
    }

    return new HashMap<K, V>() {{
        IntStream.range(0, keys.size()).forEach(index -> put(keys.get(index), values.get(index)));
    }};
}

Nota: No se recomienda usar esto para todo, ya que esto hace una clase anónima cada vez que usa esto.

NotNV6
fuente
1

JAVA 8

En java 8 simple también tienes la posibilidad de usar Streams/Collectorspara hacer el trabajo.

Map<String, String> myMap = Stream.of(
         new SimpleEntry<>("key1", "value1"),
         new SimpleEntry<>("key2", "value2"),
         new SimpleEntry<>("key3", "value3"))
        .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));

Esto tiene la ventaja de no crear una clase anónima.

Tenga en cuenta que las importaciones son:

import static java.util.stream.Collectors.toMap;
import java.util.AbstractMap.SimpleEntry;

Por supuesto, como se señaló en otras respuestas, en Java 9 en adelante tienes formas más simples de hacer lo mismo.

Johnny Willer
fuente
0

Desafortunadamente, usar varargs si el tipo de las claves y los valores no son los mismos no es muy razonable, ya que tendrías que usar Object...y perder completamente la seguridad del tipo. Si siempre desea crear, por ejemplo Map<String, String>, un , por supuesto, untoMap(String... args) sería posible, pero no muy bonito, ya que sería fácil mezclar claves y valores, y un número impar de argumentos sería inválido.

Puede crear una subclase de HashMap que tenga un método encadenable como

public class ChainableMap<K, V> extends HashMap<K, V> {
  public ChainableMap<K, V> set(K k, V v) {
    put(k, v);
    return this;
  }
}

y úsalo como new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Otro enfoque es usar el patrón de construcción común:

public class MapBuilder<K, V> {
  private Map<K, V> mMap = new HashMap<>();

  public MapBuilder<K, V> put(K k, V v) {
    mMap.put(k, v);
    return this;
  }

  public Map<K, V> build() {
    return mMap;
  }
}

y úsalo como new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Sin embargo, la solución que he usado de vez en cuando utiliza varargs y la Pairclase:

public class Maps {
  public static <K, V> Map<K, V> of(Pair<K, V>... pairs) {
    Map<K, V> = new HashMap<>();

    for (Pair<K, V> pair : pairs) {
      map.put(pair.first, pair.second);
    }

    return map;
  }
}

Map<String, Object> map = Maps.of(Pair.create("a", 1), Pair.create("b", "foo");

La verbosidad de Pair.create()me molesta un poco, pero esto funciona bastante bien. Si no le importa las importaciones estáticas, por supuesto, podría crear un ayudante:

public <K, V> Pair<K, V> p(K k, V v) {
  return Pair.create(k, v);
}

Map<String, Object> map = Maps.of(p("a", 1), p("b", "foo");

(En lugar de lo que Pairuno podría imaginar usar Map.Entry, pero como es una interfaz, requiere una clase de implementación y / o un método de fábrica auxiliar. Tampoco es inmutable y contiene otra lógica que no es útil para esta tarea).

JHH
fuente
0

Puede usar Streams en Java 8 (esto es un ejemplo de Set):

@Test
public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
    Set<String> countries = Stream.of("India", "USSR", "USA")
      .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

    assertTrue(countries.contains("India"));
}

Ref: https://www.baeldung.com/java-double-brace-initialization

Robocida
fuente
0

Si necesita colocar solo un par clave-valor, puede usar Collections.singletonMap (clave, valor);

Balakrishna Kudikala
fuente
1
formatear el código hace que la publicación sea mucho más legible
Renato