construtor para HashMap

109

O Guava nos fornece ótimos métodos de fábrica para tipos Java, como Maps.newHashMap().

Mas também existem construtores para mapas java?

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

Respostas:

20

Uma vez que a Mapinterface Java 9 contém:

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

As limitações desses métodos de fábrica são:

  • não pode conter nulls como chaves e / ou valores (se você precisar armazenar nulos, dê uma olhada em outras respostas)
  • produzir mapas imutáveis

Se precisarmos de um mapa mutável (como HashMap), podemos usar seu construtor de cópia e deixá-lo copiar o conteúdo do mapa criado viaMap.of(..)

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
Pshemo
fonte
2
Observe que os métodos Java 9 não permitem nullvalores, o que pode ser um problema dependendo do caso de uso.
Por Lundberg
@JoshM. O IMO Map.of(k1,v1, k2,v2, ...)pode ser usado com segurança quando não temos muitos valores. Para uma quantidade maior de valores Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)nos dá um código mais legível que é menos sujeito a erros (a menos que eu tenha entendido mal).
Pshemo
Você entendeu bem. O primeiro é realmente nojento para mim; Eu me recuso a usar isso!
Josh M.
164

Não existe tal coisa para HashMaps, mas você pode criar um ImmutableMap com um construtor:

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

E se você precisar de um mapa mutável, basta alimentá-lo para o construtor HashMap.

final Map<String, Integer> m = Maps.newHashMap(
    ImmutableMap.<String, Integer>builder().
        put("a", 1).
        put("b", 2).
        build());
Sean Patrick Floyd
fonte
43
ImmutableMapnão suporta nullvalores. Portanto, há uma limitação dessa abordagem: você não pode definir valores em seu HashMappara null.
vitaly
5
sean-patrick-floyd Bem, um exemplo prático: NamedParameterJdbcTemplate do Spring espera um mapa de valores codificados por nomes de parâmetros. Digamos que eu queira usar NamedParameterJdbcTemplate para definir um valor de coluna como nulo. Não vejo: a) como é cheiro de código; b) como usar o padrão de objeto nulo aqui
vitaly
2
@vitaly não pode argumentar contra isso
Sean Patrick Floyd
2
Há algo de errado em usar o new HashMapconstrutor Java em vez do Maps.newHashMapmétodo estático ?
CorayThan
1
@CorayThan - Jonik está correto, é apenas um atalho baseado na inferência de tipo estático. stackoverflow.com/a/13153812
AndersDJohnson de
46

Não é bem um construtor, mas usa um inicializador:

Map<String, String> map = new HashMap<String, String>() {{
    put("a", "1");
    put("b", "2");
}};
Johan Sjöberg
fonte
Esperar. Isso não seria map instanceof HashMapfalso? Parece uma ideia não muito boa.
Elazar Leibovich
3
@Elazar map.getClass()==HashMap.classretornará falso. Mas isso é um teste estúpido de qualquer maneira. HashMap.class.isInstance(map)deve ser preferido, e isso retornará verdadeiro.
Sean Patrick Floyd,
59
Dito isso: ainda acho que essa solução é má.
Sean Patrick Floyd,
11
Este é um inicializador de instância, não um inicializador estático. Ele é executado após o construtor do super, mas antes do corpo do construtor, para cada construtor na classe. O ciclo de vida não é muito conhecido e, por isso, evito esse idioma.
Joe Coder
14
Esta é uma solução muito ruim e deve ser evitada: stackoverflow.com/a/27521360/3253277
Alexandre DuBreuil
36

Isso é semelhante à resposta aceita, mas um pouco mais clara, a meu ver:

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

Existem várias variações do método acima e são ótimas para fazer mapas estáticos, imutáveis ​​e imutáveis.

Jake Toronto
fonte
4
Eu pedi um construtor. Você está limitado a um punhado de elementos.
Elazar Leibovich
Bom e limpo, mas me faz sentir falta do operador => do Perl ... o que é uma sensação estranha.
Aaron Maenpaa,
10

Aqui está um muito simples ...

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

então

import static FluentHashMap.map;

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

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

culmat
fonte
Gosto da simplicidade da sua abordagem. Especialmente porque
estamos em
Parece muito legal, obrigado. @MiladNaseri é uma loucura que o JDK não tenha algo assim em sua API ainda, que pena.
improvável
9

Um construtor de mapa simples é fácil de escrever:

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());
        }
    }
}
Agnes
fonte
6

Você pode usar:

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

Não é tão elegante e legível, mas faz o trabalho.

Elazar Leibovich
fonte
1
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2);, Melhor?
Ischin,
Igual ao construtor, mas com quantidade limitada de dados, pois é implementado com sobrecargas. Se você tem apenas alguns elementos - acho que é preferível.
Elazar Leibovich
4

HashMapé mutável; não há necessidade de um construtor.

Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
ColinD
fonte
E se você quiser inicializar um campo com ele? Toda lógica na mesma linha é melhor do que a lógica espalhada entre o campo e o c'tor.
Elazar Leibovich
@Elazar: Se você deseja inicializar um campo com valores específicos que são conhecidos em tempo de compilação como este, você geralmente deseja que esse campo seja imutável e deve ser usado ImmutableSet. Se você realmente quiser que ele seja mutável, pode inicializá-lo no construtor ou em um bloco inicializador de instância ou em um bloco inicializador estático, se for um campo estático.
ColinD
1
Er, deveria ter dito ImmutableMaplá obviamente.
ColinD
Acho que não. Prefiro ver a inicialização na mesma linha de definição, do que colocá-los em uma inicialização {{init();}}não estática (não no construtor, já que outro construtor pode esquecê-la). E é bom que seja uma espécie de ação atômica. Se o mapa for volátil, inicializá-lo com um construtor garante que ele esteja sempre nullno estado final, nunca preenchido pela metade.
Elazar Leibovich,
1

Você pode usar a API fluente nas coleções do Eclipse :

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

Aqui está um blog com mais detalhes e exemplos.

Nota: Eu sou um committer para Eclipse Collections.

Donald Raab
fonte
0

Eu tinha um requisito semelhante há um tempo. Não tem nada a ver com goiaba, mas você pode fazer algo assim para ser capaz de construir umMap usando um construtor fluente.

Crie uma classe base que estenda 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;
    }
}

Em seguida, crie o construtor fluente com métodos que atendam às suas necessidades:

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...

}

Você pode então implementá-lo assim:

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

Isso é algo que eu sempre quis, especialmente durante a configuração de acessórios de teste. Finalmente, decidi escrever um construtor fluente simples que pudesse construir qualquer implementação 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
fonte
0

Aqui está um que escrevi

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

Você o usa assim:

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
fonte
0

Usando java 8:

Esta é uma abordagem do 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);
    }
}

Como usar:

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
fonte
0

Tem ImmutableMap.builder()na Goiaba.

Michal
fonte
0

Underscore-java pode construir 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
fonte