Como inicializar diretamente um HashMap (de maneira literal)?

1094

Existe alguma maneira de inicializar um Java HashMap como este?

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

Qual seria a sintaxe correta? Eu não encontrei nada sobre isso. Isso é possível? Estou procurando a maneira mais curta / rápida de colocar alguns valores "finais / estáticos" em um mapa que nunca mudam e são conhecidos antecipadamente ao criar o Mapa.

jens
fonte
Intimamente relacionados: stackoverflow.com/questions/507602/... (Ambas as perguntas são sobre inicializar um mapa constante com estática, os valores finais.)
Jonik
Em Java 9: techiedelight.com/initialize-map-java9
Kamil Tomasz Jarmusik
Se você usar apache.commons.collections, poderá usar commons.apache.org/proper/commons-collections/javadocs/…
ax.

Respostas:

1346

Todas versões

Caso você precise de apenas uma entrada: Existe Collections.singletonMap("key", "value").

Para Java Versão 9 ou superior:

Sim, isso é possível agora. No Java 9, foram adicionados alguns métodos de fábrica que simplificam a criação 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")
);

No exemplo acima, tanto teste test2será o mesmo, apenas com maneiras diferentes de expressar o Mapa. O Map.ofmétodo é definido para até dez elementos no mapa, enquanto o Map.ofEntriesmétodo não terá esse limite.

Observe que, nesse caso, o mapa resultante será um mapa imutável. Se você deseja que o mapa seja mutável, você pode copiá-lo novamente, por exemplo, usandomutableMap = new HashMap<>(Map.of("a", "b"));

(Veja também JEP 269 e Javadoc )

Para até Java Versão 8:

Não, você precisará adicionar todos os elementos manualmente. Você pode usar um inicializador em uma subclasse anônima para tornar a sintaxe um pouco mais curta:

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

No entanto, a subclasse anônima pode introduzir comportamento indesejado em alguns casos. Isso inclui, por exemplo:

  • Ele gera uma classe adicional que aumenta o consumo de memória, consumo de espaço em disco e tempo de inicialização
  • No caso de um método não estático: contém uma referência ao objeto que o método de criação foi chamado. Isso significa que o objeto da classe externa não pode ser coletado como lixo enquanto o objeto de mapa criado ainda é referenciado, bloqueando assim a memória adicional

O uso de uma função para inicialização também permitirá gerar um mapa em um inicializador, mas evita efeitos colaterais desagradáveis:

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;
}
ianque
fonte
3
Isso não vai funcionar se você quiser rubricar os elementos em uma função ...
Michael
9
@ Michael: Bem, sim, se você quiser usar uma função do que você não pode usar uma não-função. Mas por que você quer?
yankee
6
e para os casos em que você precisa de um mapa com uma única entrada há Collections.singletonMap():)
skwisgaar
3
Agora que o Java 9 estável foi lançado, prefiro este link para o Javadoc . E +1 porque uma dependência a menos!
Franklin Yu
3
Onde o Java 9 está entrydocumentado?
Nobar
1030

Essa é uma maneira.

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

No entanto, você deve ter cuidado e entender o código acima (ele cria uma nova classe que herda do HashMap). Portanto, você deve ler mais aqui: http://www.c2.com/cgi/wiki?DoubleBraceInitialization , ou simplesmente usar o Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
gregory561
fonte
72
Ele funciona, mas é feio e tem efeitos colaterais invisíveis que o usuário deve entender antes de fazê-lo - por exemplo, gerando uma classe anônima inteira no local.
jprete
96
Sim, foi assim que escrevi sobre ser cuidadoso e dei um link para a descrição.
gregory561
6
Ótimo link. A referência nesse link para GreencoddsTenthRuleOfProgramming vale a leitura.
Michaelok 16/05
19
você pode adicionar "como ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" como o método "of" é limitado a 5 pares no máximo?
kommradHomer
342

Se você permitir bibliotecas de terceiros, poderá usar o ImmutableMap do Guava para obter uma brevidade literal:

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

Isso funciona para até 5 pares de chave / valor , caso contrário, você pode usar seu construtor :

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


  • observe que a implementação do ImmutableMap do Guava difere da implementação do HashMap do Java (mais notavelmente é imutável e não permite valores / chaves nulos)
  • para obter mais informações, consulte o artigo do guia do usuário do Guava sobre seus tipos imutáveis ​​de coleção
Jens Hoffmann
fonte
26
Além disso, a goiaba possui ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius 9/10
17
ImmutableMap não é o mesmo que um HashMap, pois falhará em valores nulos, enquanto o mapa HashMap não.
Gewthen 6/03/2014
2
Apenas para ajudar outras pessoas que possam enfrentar esse problema. É necessário digitar o construtor para torná-lo um Map <String, String>, assim: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Thiago9
isso é demais Jens!
gaurav
105

Não existe uma maneira direta de fazer isso - o Java não possui literais de mapa (ainda - acho que eles foram propostos para o Java 8).

Algumas pessoas assim:

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

Isso cria uma subclasse anônima do HashMap, cujo inicializador de instância coloca esses valores. (A propósito, um mapa não pode conter o dobro do mesmo valor, seu segundo put substituirá o primeiro. Usarei valores diferentes para os próximos exemplos.)

A maneira normal seria esta (para uma variável local):

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

Se o seu testmapa for uma variável de instância, coloque a inicialização em um construtor ou inicializador de instância:

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

Se o seu testmapa for uma variável de classe, coloque a inicialização em um inicializador estático:

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

Se você deseja que seu mapa nunca mude, após a inicialização, envolva seu mapa Collections.unmodifiableMap(...). Você também pode fazer isso em um 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);
}

(Não tenho certeza se agora você pode fazer a testfinal ... experimente e relate aqui.)

Paŭlo Ebermann
fonte
61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};
Shaggy Frog
fonte
Simples e direto ao ponto. Eu acho que isso com uma seção de comentários estendida seria a melhor resposta.
Ooolala
15
Existem implicações de memória que devem ser observadas. blog.jooq.org/2014/12/08/…
Amalgovinus
1
@ Amálgovinus Basicamente, ao criar uma nova subclasse, você está codificando os argumentos de tipo HashMapnessa subclasse. Isso só funciona se você realmente os fornecer. (Com um novo HashMap (vazio), os argumentos de tipo não são relevantes.)
Paulo Ebermann
1
I como a limpeza, mas ele cria classe anônima desnecessária e tem os problemas descritos aqui: c2.com/cgi/wiki?DoubleBraceInitialization
Udachny
1
@hello_its_me: porque é o mesmo que stackoverflow.com/a/6802512/1386911 resposta, apenas com a formatação diferente. E, neste caso, essa formatação estendida não tem valor adicional além do formato compacto para facilitar a leitura.
Daniel Hári 31/10
44

Uma alternativa, usando classes e varargs Java 7 simples: crie uma classe HashMapBuildercom 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 o método como este:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
Aerthel
fonte
Eu escrevi uma resposta inspirada na sua: stackoverflow.com/questions/507602/…
GeroldBroser reinstala Monica em
1
Outra solução com o Apache Utils que nunca é mencionada, mas é legível, usando as versões anteriores do Java: MapUtils.putAll (new HashMap <String, String> (), new Object [] {"My key", "my value", ...
Rolintocour 25/10/19
4

tl; dr

Use Map.of…métodos no Java 9 e posterior.

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

Map.of

O Java 9 adicionou uma série de Map.ofmétodos estáticos para fazer exatamente o que você deseja: Instancie um imutável Mapusando a sintaxe literal .

O mapa (uma coleção de entradas) é imutável, portanto você não pode adicionar ou remover entradas após instanciar. Além disso, a chave e o valor de cada entrada são imutáveis, não podem ser alterados. Consulte o Javadoc para outras regras, como NULLs não permitidos, chaves duplicadas não permitidas e a ordem de iteração dos mapeamentos é arbitrária.

Vejamos esses métodos, usando alguns dados de amostra para um mapa do dia da semana para uma pessoa que esperamos que funcione nesse dia.

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

Map.of()

Map.ofcria um vazio Map. Não modificável, então você não pode adicionar entradas. Aqui está um exemplo desse mapa, vazio sem entradas.

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

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)Existem vários métodos que levam de 1 a 10 pares de valores-chave. Aqui está um exemplo de duas entradas.

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

weekendWorker.toString (): {DOMINGO = Pessoa {nome = 'Bob'}, SÁBADO = Pessoa {nome = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )leva qualquer número de objetos implementando a Map.Entryinterface. O Java agrupa duas classes implementando essa interface, uma mutável e a outra imutável: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Mas não precisamos especificar uma classe concreta. Nós apenas precisamos chamar Map.entry( k , v )método, passar nossa chave e nosso valor e retornar um objeto de uma Map.Entryinterface de implementação de alguma classe .

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

weekdayWorker.toString (): {QUARTA-FEIRA = Pessoa {nome = 'Bob'}, TERÇA-FEIRA = Pessoa {nome = 'Bob'}, QUINTA-FEIRA = Pessoa {nome = 'Carol'}, SEXTA-FEIRA = Pessoa {nome = 'Carol'} , SEGUNDA-FEIRA = Pessoa {name = 'Alice'}}

Map.copyOf

O Java 10 adicionou o método Map.copyOf. Passe um mapa existente, receba de volta uma cópia imutável desse mapa.

Notas

Observe que a ordem do iterador dos mapas produzidos via nãoMap.of é garantida. As entradas têm uma ordem arbitrária. Não escreva código com base no pedido visto, pois a documentação avisa que o pedido está sujeito a alterações.

Observe que todos esses Map.of…métodos retornam um Mapde uma classe não especificada . A classe concreta subjacente pode até variar de uma versão do Java para outra. Esse anonimato permite que o Java escolha entre várias implementações, o que for melhor para seus dados específicos. Por exemplo, se suas chaves vierem de uma enumeração , o Java pode usar uma EnumMapsob as cobertas.

Basil Bourque
fonte
1

Você poderia criar seu próprio Map.ofmétodo (que está disponível apenas no Java 9 e superior) facilmente de duas maneiras fáceis

Faça com uma quantidade definida de parâmetros

Exemplo

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

Faça usando uma lista

Você também pode fazer isso usando uma lista, em vez de criar muitos métodos para um determinado conjunto de parâmetros.

Exemplo

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 Não é recomendável usar isso para tudo, pois isso cria uma classe anônima toda vez que você o usa.

NotNV6
fonte
1

JAVA 8

No java 8 simples, você também tem a possibilidade de usar Streams/Collectorspara fazer o trabalho.

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

Isso tem a vantagem de não criar uma classe anônima.

Observe que as importações são:

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

Obviamente, como observado em outras respostas, no java 9 em diante, você tem maneiras mais simples de fazer o mesmo.

Johnny Willer
fonte
0

Infelizmente, o uso de varargs se o tipo de chaves e valores não for o mesmo não é muito razoável, pois você precisaria usar Object...e perder completamente a segurança do tipo. Se você sempre deseja criar, por exemplo Map<String, String>, a , é claro que a toMap(String... args)seria possível, mas não muito bonita, pois seria fácil misturar chaves e valores, e um número ímpar de argumentos seria inválido.

Você pode criar uma subclasse de HashMap que tenha um método encadeado como

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

e use-o como new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Outra abordagem é usar o padrão do construtor comum:

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

e use-o como new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

No entanto, a solução que usei de vez em quando utiliza varargs e a Pairclasse:

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

A verbosidade de Pair.create()me incomoda um pouco, mas isso funciona muito bem. Se você não se importa com importações estáticas, é claro que você pode criar um auxiliar:

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

(Em vez de Pairse imaginar usando Map.Entry, mas como é uma interface, é necessária uma classe de implementação e / ou um método auxiliar de fábrica. Também não é imutável e contém outra lógica que não é útil para esta tarefa.)

JHH
fonte
0

Você pode usar Streams In Java 8 (este é o exemplo 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

Robocide
fonte
0

Se você precisar colocar apenas um par de valores-chave, poderá usar Collections.singletonMap (key, value);

Balakrishna Kudikala
fonte
1
código de formatação torna a postagem muito mais legível
Renato