constructor para HashMap

109

Guava nos proporciona excelentes métodos de fábrica para tipos de Java, como Maps.newHashMap().

¿Pero también hay constructores para Java Maps?

HashMap<String,Integer> m = Maps.BuildHashMap.
    put("a",1).
    put("b",2).
    build();
Elazar Leibovich
fuente

Respuestas:

20

Dado que la Mapinterfaz de Java 9 contiene:

  • Map.of(k1,v1, k2,v2, ..)
  • Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ..).

Las limitaciones de esos métodos de fábrica son que:

  • no puede contener nulls como claves y / o valores (si necesita almacenar nulos, eche un vistazo a otras respuestas)
  • producir mapas inmutables

Si necesitamos un mapa mutable (como HashMap), podemos usar su constructor de copia y dejar que copie el contenido del mapa creado a través deMap.of(..)

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
Pshemo
fuente
2
Tenga en cuenta que los métodos de Java 9 no permiten nullvalores, lo que puede ser un problema según el caso de uso.
Per Lundberg
@JoshM. IMO Map.of(k1,v1, k2,v2, ...)se puede utilizar de forma segura cuando no tenemos muchos valores. Para una mayor cantidad de valores, Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)nos da un código más legible que es menos propenso a errores (a menos que te haya entendido mal).
Pshemo
Lo entendiste bien. Lo primero es realmente asqueroso para mí; ¡Me niego a usarlo!
Josh M.
164

No existe tal cosa para HashMaps, pero puede crear un ImmutableMap con un constructor:

final Map<String, Integer> m = ImmutableMap.<String, Integer>builder().
      put("a", 1).
      put("b", 2).
      build();

Y si necesita un mapa mutable, puede simplemente enviarlo al constructor HashMap.

final Map<String, Integer> m = Maps.newHashMap(
    ImmutableMap.<String, Integer>builder().
        put("a", 1).
        put("b", 2).
        build());
Sean Patrick Floyd
fuente
43
ImmutableMapno admite nullvalores. Por lo tanto, hay una limitación de este enfoque: no puede establecer valores en su HashMapto null.
vitaly
5
sean-patrick-floyd Bueno, un ejemplo práctico: Spring's NamedParameterJdbcTemplate espera un mapa de valores codificados por nombres de parámetros. Digamos que quiero usar NamedParameterJdbcTemplate para establecer un valor de columna en nulo. No veo: a) cómo es un olor a código; b) cómo usar el patrón de objeto nulo aquí
vitaly
2
@vitaly no puede discutir con eso
Sean Patrick Floyd
2
¿Hay algún problema con el uso del new HashMapconstructor de Java en lugar del Maps.newHashMapmétodo estático ?
CorayThan
1
@CorayThan: Jonik tiene razón, es solo un atajo que se basa en la inferencia de tipo estático. stackoverflow.com/a/13153812
AndersDJohnson
46

No es un constructor, pero usa un inicializador:

Map<String, String> map = new HashMap<String, String>() {{
    put("a", "1");
    put("b", "2");
}};
Johan Sjöberg
fuente
Espere. ¿No sería eso map instanceof HashMapfalso? Parece una idea no tan buena.
Elazar Leibovich
3
@Elazar map.getClass()==HashMap.classdevolverá falso. Pero esa es una prueba estúpida de todos modos. HashMap.class.isInstance(map)debería ser preferido, y eso volverá verdadero.
Sean Patrick Floyd
59
Dicho esto: sigo pensando que esta solución es mala.
Sean Patrick Floyd
11
Este es un inicializador de instancia, no un inicializador estático. Se ejecuta después del constructor del super, pero antes del cuerpo del constructor, para cada constructor de la clase. El ciclo de vida no es muy conocido y por eso evito este idioma.
Joe Coder
14
Esta es una muy mala solución y debe evitarse: stackoverflow.com/a/27521360/3253277
Alexandre DuBreuil
36

Esto es similar a la respuesta aceptada, pero un poco más limpia, en mi opinión:

ImmutableMap.of("key1", val1, "key2", val2, "key3", val3);

Hay varias variaciones del método anterior y son excelentes para hacer mapas estáticos, inmutables e inmutables.

Jake Toronto
fuente
4
Pedí un constructor. Estás limitado a un puñado de elementos.
Elazar Leibovich
Agradable y limpio, pero me hace añorar el operador => de Perl ... lo cual es una sensación extraña.
Aaron Maenpaa
10

Aquí hay uno muy simple ...

public class FluentHashMap<K, V> extends java.util.HashMap<K, V> {
  public FluentHashMap<K, V> with(K key, V value) {
    put(key, value);
    return this;
  }

  public static <K, V> FluentHashMap<K, V> map(K key, V value) {
    return new FluentHashMap<K, V>().with(key, value);
  }
}

luego

import static FluentHashMap.map;

HashMap<String, Integer> m = map("a", 1).with("b", 2);

Ver https://gist.github.com/culmat/a3bcc646fa4401641ac6eb01f3719065

culmat
fuente
Me gusta la sencillez de su enfoque. Especialmente porque
estamos en
Se ve muy bien, gracias. @MiladNaseri es una locura que JDK aún no tenga algo como esto en su API, qué lástima.
improbable
9

Un simple constructor de mapas es trivial de escribir:

public class Maps {

    public static <Q,W> MapWrapper<Q,W> map(Q q, W w) {
        return new MapWrapper<Q, W>(q, w);
    }

    public static final class MapWrapper<Q,W> {
        private final HashMap<Q,W> map;
        public MapWrapper(Q q, W w) {
            map = new HashMap<Q, W>();
            map.put(q, w);
        }
        public MapWrapper<Q,W> map(Q q, W w) {
            map.put(q, w);
            return this;
        }
        public Map<Q,W> getMap() {
            return map;
        }
    }

    public static void main(String[] args) {
        Map<String, Integer> map = Maps.map("one", 1).map("two", 2).map("three", 3).getMap();
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }
    }
}
Inés
fuente
6

Puedes usar:

HashMap<String,Integer> m = Maps.newHashMap(
    ImmutableMap.of("a",1,"b",2)
);

No es tan elegante y legible, pero funciona.

Elazar Leibovich
fuente
1
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2);, ¿mejor?
lschin
Igual que el constructor, pero con una cantidad limitada de datos, ya que se implementa con sobrecargas. Si tiene solo unos pocos elementos, supongo que es preferible.
Elazar Leibovich
4

HashMapes mutable; no hay necesidad de constructor.

Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
ColinD
fuente
¿Qué pasa si desea inicializar un campo con él? Toda la lógica en la misma línea es mejor que la lógica dispersa entre campo y c'tor.
Elazar Leibovich
@Elazar: Si desea inicializar un campo con valores específicos que se conocen en tiempo de compilación como este, generalmente desea que ese campo sea inmutable y debe usar ImmutableSet. Si realmente desea que sea mutable, puede inicializarlo en el constructor o en un bloque inicializador de instancia o en un bloque inicializador estático si es un campo estático.
ColinD
1
Er, debería haber dicho ImmutableMapeso obviamente.
ColinD
No lo creo. Prefiero ver la inicialización en la misma línea de definición, luego tenerlos metidos en una inicialización {{init();}}no estática (no en el constructor, ya que otro constructor podría olvidarlo). Y es bueno que sea una especie de acción atómica. Si el mapa es volátil, al inicializarlo con un constructor, asegúrese de que siempre esté nullo en el estado final, nunca a la mitad.
Elazar Leibovich
1

Puede utilizar la API fluida en Eclipse Collections :

Map<String, Integer> map = Maps.mutable.<String, Integer>empty()
        .withKeyValue("a", 1)
        .withKeyValue("b", 2);

Assert.assertEquals(Maps.mutable.with("a", 1, "b", 2), map);

Aquí hay un blog con más detalles y ejemplos.

Nota: Soy un comprometido con las colecciones de Eclipse.

Donald Raab
fuente
0

Hace un tiempo tuve un requisito similar. No tiene nada que ver con Guava, pero puedes hacer algo como esto para poder construir limpiamente Mapusando un constructor fluido.

Cree una clase base que amplíe Map.

public class FluentHashMap<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 4857340227048063855L;

    public FluentHashMap() {}

    public FluentHashMap<K, V> delete(Object key) {
        this.remove(key);
        return this;
    }
}

Luego, cree el constructor fluido con métodos que se adapten a sus necesidades:

public class ValueMap extends FluentHashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public ValueMap() {}

    public ValueMap withValue(String key, String val) {
        super.put(key, val);
        return this;
    }

... Add withXYZ to suit...

}

Luego puede implementarlo así:

ValueMap map = new ValueMap()
      .withValue("key 1", "value 1")
      .withValue("key 2", "value 2")
      .withValue("key 3", "value 3")
tarka
fuente
0

Esto es algo que siempre quise, especialmente al configurar dispositivos de prueba. Finalmente, decidí escribir un constructor sencillo y fluido propio que pudiera construir cualquier implementación de mapa: https://gist.github.com/samshu/b471f5a2925fa9d9b718795d8bbdfe42#file-mapbuilder-java

    /**
     * @param mapClass Any {@link Map} implementation type. e.g., HashMap.class
     */
    public static <K, V> MapBuilder<K, V> builder(@SuppressWarnings("rawtypes") Class<? extends Map> mapClass)
            throws InstantiationException,
            IllegalAccessException {
        return new MapBuilder<K, V>(mapClass);
    }

    public MapBuilder<K, V> put(K key, V value) {
        map.put(key, value);
        return this;
    }

    public Map<K, V> build() {
        return map;
    }
aathif
fuente
0

Aquí hay uno que escribí

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class MapBuilder<K, V> {

    private final Map<K, V> map;

    /**
     * Create a HashMap builder
     */
    public MapBuilder() {
        map = new HashMap<>();
    }

    /**
     * Create a HashMap builder
     * @param initialCapacity
     */
    public MapBuilder(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * Create a Map builder
     * @param mapFactory
     */
    public MapBuilder(Supplier<Map<K, V>> mapFactory) {
        map = mapFactory.get();
    }

    public MapBuilder<K, V> put(K key, V value) {
        map.put(key, value);
        return this;
    }

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

    /**
     * Returns an unmodifiable Map. Strictly speaking, the Map is not immutable because any code with a reference to
     * the builder could mutate it.
     *
     * @return
     */
    public Map<K, V> buildUnmodifiable() {
        return Collections.unmodifiableMap(map);
    }
}

Lo usas así:

Map<String, Object> map = new MapBuilder<String, Object>(LinkedHashMap::new)
    .put("event_type", newEvent.getType())
    .put("app_package_name", newEvent.getPackageName())
    .put("activity", newEvent.getActivity())
    .build();
Dónal
fuente
0

Usando java 8:

Este es un enfoque de Java-9 Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)

public class MapUtil {
    import static java.util.stream.Collectors.toMap;

    import java.util.AbstractMap.SimpleEntry;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.stream.Stream;

    private MapUtil() {}

    @SafeVarargs
    public static Map<String, Object> ofEntries(SimpleEntry<String, Object>... values) {
        return Stream.of(values).collect(toMap(Entry::getKey, Entry::getValue));
    }

    public static SimpleEntry<String, Object> entry(String key, Object value) {
        return new SimpleEntry<String, Object>(key, value);
    }
}

Cómo utilizar:

import static your.package.name.MapUtil.*;

import java.util.Map;

Map<String, Object> map = ofEntries(
        entry("id", 1),
        entry("description", "xyz"),
        entry("value", 1.05),
        entry("enable", true)
    );
Leandro Fantinel
fuente
0

Hay ImmutableMap.builder()en Guayaba.

Michal
fuente
0

Underscore-java puede crear hashmap.

Map<String, Object> value = U.objectBuilder()
        .add("firstName", "John")
        .add("lastName", "Smith")
        .add("age", 25)
        .add("address", U.arrayBuilder()
            .add(U.objectBuilder()
                .add("streetAddress", "21 2nd Street")
                .add("city", "New York")
                .add("state", "NY")
                .add("postalCode", "10021")))
        .add("phoneNumber", U.arrayBuilder()
            .add(U.objectBuilder()
                .add("type", "home")
                .add("number", "212 555-1234"))
            .add(U.objectBuilder()
                .add("type", "fax")
                .add("number", "646 555-4567")))
        .build();
    // {firstName=John, lastName=Smith, age=25, address=[{streetAddress=21 2nd Street,
    // city=New York, state=NY, postalCode=10021}], phoneNumber=[{type=home, number=212 555-1234},
    // {type=fax, number=646 555-4567}]}
Valentyn Kolesnikov
fuente