Como posso inicializar um mapa estático?

Como você inicializaria um mapa estático em Java?

Método um: inicializador estático
Método dois: inicializador de instância (subclass anônima) ou algum outro método?

Quais são os prós e contras de cada um?

Aqui está um exemplo ilustrando dois methods:

import java.util.HashMap; import java.util.Map; public class Test { private static final Map myMap = new HashMap(); static { myMap.put(1, "one"); myMap.put(2, "two"); } private static final Map myMap2 = new HashMap(){ { put(1, "one"); put(2, "two"); } }; } 

O inicializador de instância é apenas açúcar sintático, neste caso, certo? Eu não vejo porque você precisa de uma class extra anônima apenas para inicializar. E não funcionará se a turma que está sendo criada for final.

Você também pode criar um mapa imutável usando um inicializador estático:

 public class Test { private static final Map myMap; static { Map aMap = ....; aMap.put(1, "one"); aMap.put(2, "two"); myMap = Collections.unmodifiableMap(aMap); } } 

Eu gosto do jeito Guava de inicializar um mapa estático e imutável:

 static final Map MY_MAP = ImmutableMap.of( 1, "one", 2, "two" ); 

Como você pode ver, é muito conciso (por causa dos methods convenientes de fábrica no ImmutableMap ).

Se você quiser que o mapa tenha mais de 5 inputs, não poderá mais usar ImmutableMap.of() . Em vez disso, tente ImmutableMap.builder() ao longo destas linhas:

 static final Map MY_MAP = ImmutableMap.builder() .put(1, "one") .put(2, "two") // ... .put(15, "fifteen") .build(); 

Para saber mais sobre os benefícios dos utilitários de coleta imutável da Guava, consulte Coleções imutáveis ​​explicadas no Guava User Guide .

(Um subconjunto de) Guava costumava ser chamado de Google Collections . Se você ainda não está usando esta biblioteca em seu projeto Java, eu recomendo fortemente que você experimente! A Guava tornou-se rapidamente uma das libs de terceiros gratuitas mais populares e úteis para Java, já que outros usuários SO concordam . (Se você é novo, existem alguns excelentes resources de aprendizado por trás desse link.)


Update (2015) : Quanto ao Java 8 , bem, eu ainda usaria a abordagem do Guava porque é muito mais limpo do que qualquer outra coisa. Se você não quiser a dependência do Guava, considere um método init antigo . O hack com array bidimensional e Stream API é bem feio se você me perguntar, e fica mais feio se você precisa criar um Map cujas chaves e valores não são do mesmo tipo (como Map na questão).

Quanto ao futuro da Guava em geral, no que diz respeito ao Java 8, Louis Wasserman disse isso em 2014, e [ update ] em 2016 foi anunciado que o Guava 21 exigirá e suportará adequadamente o Java 8 .


Update (2016) : Como aponta Tagir Valeev , o Java 9 finalmente fará isso limpo usando nada além do JDK puro, adicionando methods de fábrica convenientes para collections:

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

Eu usaria:

 public class Test { private static final Map MY_MAP = createMap(); private static Map createMap() { Map result = new HashMap(); result.put(1, "one"); result.put(2, "two"); return Collections.unmodifiableMap(result); } } 
  1. evita aulas anônimas, que eu pessoalmente considero um estilo ruim, e evito
  2. isso torna a criação do mapa mais explícita
  3. torna o mapa inalterável
  4. como MY_MAP é constante, eu nomearia como constante

O Java 5 fornece essa syntax mais compacta:

 static final Map FLAVORS = new HashMap() {{ put("Up", "Down"); put("Charm", "Strange"); put("Top", "Bottom"); }}; 

Uma vantagem para o segundo método é que você pode envolvê-lo com Collections.unmodifiableMap() para garantir que nada atualizará a coleção mais tarde:

 private static final Map CONSTANT_MAP = Collections.unmodifiableMap(new HashMap() {{ put(1, "one"); put(2, "two"); }}); // later on... CONSTANT_MAP.put(3, "three"); // going to throw an exception! 

Aqui está um inicializador de mapa estático de uma linha Java 8:

 private static final Map EXTENSION_TO_MIMETYPE = Arrays.stream(new String[][] { { "txt", "text/plain" }, { "html", "text/html" }, { "js", "application/javascript" }, { "css", "text/css" }, { "xml", "application/xml" }, { "png", "image/png" }, { "gif", "image/gif" }, { "jpg", "image/jpeg" }, { "jpeg", "image/jpeg" }, { "svg", "image/svg+xml" }, }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); 

Edit: para inicializar um Map como na pergunta, você precisaria de algo como isto:

 static final Map MY_MAP = Arrays.stream(new Object[][]{ {1, "one"}, {2, "two"}, }).collect(Collectors.toMap(kv -> (Integer) kv[0], kv -> (String) kv[1])); 

Edit (2): Existe uma versão melhor, do tipo misto, do tipo i_am_zero, que usa um stream de new SimpleEntry<>(k, v) . Confira essa resposta: https://stackoverflow.com/a/37384773/3950982

No Java 9:

 private static final Map MY_MAP = Map.of(1, "one", 2, "two"); 

Veja o JEP 269 para detalhes. O JDK 9 alcançou disponibilidade geral em setembro de 2017.

Com o Eclipse Collections (anteriormente GS Collections ), todos os itens a seguir funcionarão:

 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 MAP = Maps.mutable.with(1, "one", 2, "two"); private static final MutableMap MUTABLE_MAP = Maps.mutable.with(1, "one", 2, "two"); private static final MutableMap UNMODIFIABLE_MAP = Maps.mutable.with(1, "one", 2, "two").asUnmodifiable(); private static final MutableMap SYNCHRONIZED_MAP = Maps.mutable.with(1, "one", 2, "two").asSynchronized(); private static final ImmutableMap IMMUTABLE_MAP = Maps.mutable.with(1, "one", 2, "two").toImmutable(); private static final ImmutableMap IMMUTABLE_MAP2 = Maps.immutable.with(1, "one", 2, "two"); } 

Você também pode inicializar estaticamente mapas primitivos com Coleções do 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 MUTABLE_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two"); private static final MutableIntObjectMap UNMODIFIABLE_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two") .asUnmodifiable(); private static final MutableIntObjectMap SYNCHRONIZED_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two") .asSynchronized(); private static final ImmutableIntObjectMap IMMUTABLE_INT_OBJ_MAP = IntObjectMaps.mutable.empty() .withKeyValue(1, "one") .withKeyValue(2, "two") .toImmutable(); private static final ImmutableIntObjectMap IMMUTABLE_INT_OBJ_MAP2 = IntObjectMaps.immutable.empty() .newWithKeyValue(1, "one") .newWithKeyValue(2, "two"); } 

Nota: Eu sou um committer para collections do Eclipse

Eu nunca criaria uma subclass anônima nessa situação. Os inicializadores estáticos funcionam igualmente bem, se você quiser tornar o mapa inalterável, por exemplo:

 private static final Map MY_MAP; static { MaptempMap = new HashMap(); tempMap.put(1, "one"); tempMap.put(2, "two"); MY_MAP = Collections.unmodifiableMap(tempMap); } 

Talvez seja interessante conferir as Coleções do Google , por exemplo, os vídeos que eles têm em suas páginas. Eles fornecem várias maneiras de inicializar mapas e conjuntos e também fornecem collections imutáveis.

Atualização: Esta biblioteca agora é chamada de Guava .

Eu gosto de class anônima, porque é fácil lidar com isso:

 public static final Map< ?, ?> numbers = Collections.unmodifiableMap(new HashMap() { { put(1, "some value"); //rest of code here } }); 

Há uma resposta proposta por Luke, que inicializa um mapa usando o Java 8, mas a IMHO parece feia e difícil de ler. Podemos criar um stream de inputs de mapa. Já temos duas implementações de Entry em java.util.AbstractMap que são SimpleEntry e SimpleImmutableEntry . Para este exemplo, podemos fazer uso do antigo como:

 import java.util.AbstractMap.*; private static final Map myMap = Stream.of( new SimpleEntry<>(1, "one"), new SimpleEntry<>(2, "two"), new SimpleEntry<>(3, "three"), new SimpleEntry<>(4, "four"), new SimpleEntry<>(5, "five"), new SimpleEntry<>(6, "six"), new SimpleEntry<>(7, "seven"), new SimpleEntry<>(8, "eight"), new SimpleEntry<>(9, "nine"), new SimpleEntry<>(10, "ten")) .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); 

Para o Java 9, também podemos fazer uso do Map.of como sugerido por Tagir em sua resposta aqui .

 public class Test { private static final Map myMap; static { Map aMap = ....; aMap.put(1, "one"); aMap.put(2, "two"); myMap = Collections.unmodifiableMap(aMap); } } 

Se declararmos mais de uma constante, esse código será escrito em bloco estático e isso será difícil de manter no futuro. Por isso, é melhor usar uma class anônima.

 public class Test { public static final Map numbers = Collections.unmodifiableMap(new HashMap(2, 1.0f){ { put(1, "one"); put(2, "two"); } }); } 

E é sugerido que para usar unmodifiableMap para constantes de outro modo, ele não pode ser tratado como constante.

Eu poderia sugerir fortemente o estilo “double brace initialization” em vez do estilo de bloco estático.

Alguém pode comentar que eles não gostam de class anônima, sobrecarga, desempenho, etc.

Mas isso eu considero mais é a legibilidade e manutenção do código. Neste ponto de vista, eu tenho uma chave dupla é um melhor estilo de código, em vez de método estático.

  1. Os elementos são nesteds e incorporados.
  2. É mais OO, não processual.
  3. o impacto no desempenho é muito pequeno e pode ser ignorado.
  4. Melhor suporte a tópicos do IDE (em vez de muitos blocos {} estáticos anônimos)
  5. Você salvou algumas linhas de comentário para trazê-los de relacionamento.
  6. Impedir possível vazamento de elemento / lead de instância de object não inicializado de exceção e otimizador de bytecode.
  7. Não se preocupe com a ordem de execução do bloco estático.

Além disso, você sabe que o GC da class anônima, você sempre pode convertê-lo para um HashMap normal usando o new HashMap(Map map) .

Você pode fazer isso até enfrentar outro problema. Se você fizer isso, você deve usar um outro estilo de codificação completo (por exemplo, sem estática, class de fábrica) para isso.

Como de costume, o apache-commons possui o método apropriado MapUtils.putAll (Map, Object []) :

Por exemplo, para criar um mapa de colors:

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

Se você quiser mapa unmodifiable, finalmente java 9 adicionou um método of fábrica legal para a interface do Map . Um método semelhante é adicionado ao Set, List também.

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

A class anônima que você está criando funciona bem. No entanto, você deve estar ciente de que essa é uma class interna e, portanto, conterá uma referência à instância da class em volta. Então você descobrirá que não pode fazer certas coisas com ele (usando XStream para um). Você terá alguns erros muito estranhos.

Dito isto, desde que você esteja ciente, então esta abordagem está bem. Eu uso isso na maioria das vezes para inicializar todos os tipos de collections de maneira concisa.

EDIT: Apontado corretamente nos comentários que esta é uma class estática. Obviamente eu não li isso de perto o suficiente. No entanto, meus comentários ainda se aplicam a classs internas anônimas.

Aqui está o meu favorito quando eu não quero (ou não posso) usar o ImmutableMap.of() Guava, ou se eu precisar de um Map mutável:

 public static  Map asMap(Object... keysAndValues) { return new LinkedHashMap() {{ for (int i = 0; i < keysAndValues.length - 1; i++) { put(keysAndValues[i].toString(), (A) keysAndValues[++i]); } }}; } 

É muito compacto e ignora valores de dispersão (isto é, uma chave final sem um valor).

Uso:

 Map one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal"); Map two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2)); 

Se você quer algo curto e relativamente seguro, você pode simplesmente mudar a verificação do tipo em tempo de compilation para o tempo de execução:

 static final Map map = MapUtils.unmodifiableMap( String.class, Integer.class, "cat", 4, "dog", 2, "frog", 17 ); 

Essa implementação deve detectar erros:

 import java.util.HashMap; public abstract class MapUtils { private MapUtils() { } public static  HashMap unmodifiableMap( Class< ? extends K> keyClazz, Class< ? extends V> valClazz, Object...keyValues) { return Collections.unmodifiableMap(makeMap( keyClazz, valClazz, keyValues)); } public static  HashMap makeMap( Class< ? extends K> keyClazz, Class< ? extends V> valClazz, Object...keyValues) { if (keyValues.length % 2 != 0) { throw new IllegalArgumentException( "'keyValues' was formatted incorrectly! " + "(Expected an even length, but found '" + keyValues.length + "')"); } HashMap result = new HashMap(keyValues.length / 2); for (int i = 0; i < keyValues.length;) { K key = cast(keyClazz, keyValues[i], i); ++i; V val = cast(valClazz, keyValues[i], i); ++i; result.put(key, val); } return result; } private static  T cast(Class< ? extends T> clazz, Object object, int i) { try { return clazz.cast(object); } catch (ClassCastException e) { String objectName = (i % 2 == 0) ? "Key" : "Value"; String format = "%s at index %d ('%s') wasn't assignable to type '%s'"; throw new IllegalArgumentException(String.format(format, objectName, i, object.toString(), clazz.getSimpleName()), e); } } } 

Eu prefiro usar um inicializador estático para evitar a geração de classs anônimas (o que não teria outro propósito), então vou listar dicas inicializando com um inicializador estático. Todas as soluções / dicas listadas são seguras.

Nota: A questão não diz nada sobre tornar o mapa não modificável, então vou deixar isso de lado, mas sei que isso pode ser feito facilmente com Collections.unmodifiableMap(map) .

Primeira dica

A primeira dica é que você pode fazer uma referência local ao mapa e dar um nome abreviado:

 private static final Map myMap = new HashMap<>(); static { final Map 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"); } 

Segunda dica

A segunda dica é que você pode criar um método auxiliar para adicionar inputs; você também pode tornar público esse método auxiliar se quiser:

 private static final Map 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); } 

O método auxiliar aqui não é reutilizável, pois só pode adicionar elementos ao myMap2 . Para torná-lo reutilizável, poderíamos tornar o próprio mapa um parâmetro do método auxiliar, mas, em seguida, o código de boot não seria mais curto.

Terceira dica

A terceira dica é que você pode criar uma class auxiliar reutilizável como um construtor com a funcionalidade de preenchimento. Esta é realmente uma simples class auxiliar de 10 linhas que é segura para o tipo:

 public class Test { private static final Map 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 { private final Map m; public B(Map m) { this.m = m; } public B p(K k, V v) { m.put(k, v); return this; // Return this for chaining } } 

Você pode usar StickyMap e MapEntry da Cactoos :

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

Eu não gosto da syntax do inicializador estático e não estou convencido de subclasss anônimas. Geralmente, eu concordo com todos os contras de usar inicializadores estáticos e todos os contras de usar subclasss anônimas que foram mencionadas nas respostas anteriores. Por outro lado – os profissionais apresentados nestes posts não são suficientes para mim. Eu prefiro usar o método de boot estática:

 public class MyClass { private static final Map myMap = prepareMap(); private static Map prepareMap() { Map hashMap = new HashMap<>(); hashMap.put(1, "one"); hashMap.put(2, "two"); return hashMap; } } 

Como o Java não suporta literais de mapa, as instâncias de mapa devem sempre ser explicitamente instanciadas e preenchidas.

Felizmente, é possível aproximar o comportamento de literais de mapa em Java usando methods de fábrica .

Por exemplo:

 public class LiteralMapFactory { // Creates a map from a list of entries @SafeVarargs public static  Map mapOf(Map.Entry... entries) { LinkedHashMap map = new LinkedHashMap<>(); for (Map.Entry entry : entries) { map.put(entry.getKey(), entry.getValue()); } return map; } // Creates a map entry public static  Map.Entry 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))); } } 

Saída:

{a = 1, b = 2, c = 3}

É muito mais conveniente do que criar e preencher o mapa de um elemento por vez.

Com o Java 8, eu uso o seguinte padrão:

 private static final Map MAP = Stream.of( new AbstractMap.SimpleImmutableEntry<>("key1", 1), new AbstractMap.SimpleImmutableEntry<>("key2", 2) ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 

Não é a rotunda mais concisa e um pouco, mas

  • não requer nada fora do java.util
  • É seguro e acomoda facilmente diferentes tipos de chave e valor.

Sua segunda abordagem (boot Double Brace) é pensada para ser um anti padrão , então eu iria para a primeira abordagem.

Outra maneira fácil de inicializar um mapa estático é usando esta function de utilitário:

 public static  Map mapOf(Object... keyValues) { Map 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 map1 = mapOf(1, "value1", 2, "value2"); Map map2 = mapOf("key1", "value1", "key2", "value2"); 

Nota: no Java 9 você pode usar o Map.of

Eu não vi a abordagem que eu uso (e tenho crescido a gostar) postado em todas as respostas, então aqui está:

Eu não gosto de usar inicializadores estáticos porque eles são desajeitados, e eu não gosto de classs anônimas porque está criando uma nova class para cada instância.

em vez disso, prefiro a boot que se parece com isso:

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

infelizmente, esses methods não fazem parte da biblioteca Java padrão, portanto, você precisará criar (ou usar) uma biblioteca de utilitários que defina os seguintes methods:

  public static  Map map(Map.Entry... entries) public static  Map.Entry entry(K key, V val) 

(você pode usar ‘import static’ para evitar a necessidade de prefixar o nome do método)

Achei útil fornecer methods estáticos semelhantes para as outras collections (list, set, sortedSet, sortedMap, etc.)

Não é tão bom quanto a boot do object json, mas é um passo nessa direção, no que diz respeito à legibilidade.

O JEP 269 fornece alguns methods de fábrica de conveniência para a API de collections. Esses methods de fábrica não estão na versão atual do Java, que é 8, mas estão planejados para o lançamento do Java 9.

Para o Map existem dois methods de fábrica: of e de ofEntries . Usando, você pode passar pares de chave / valor alternados. Por exemplo, para criar um Map como {age: 27, major: cs} :

 Map info = Map.of("age", 27, "major", "cs"); 

Atualmente, existem dez versões sobrecarregadas para, portanto, você pode criar um mapa contendo dez pares de chave / valor. Se você não gosta desta limitação ou chave / valores alternados, você pode usar ofEntries :

 Map info = Map.ofEntries( Map.entry("age", 27), Map.entry("major", "cs") ); 

Ambos e ofEntries retornarão um Map imutável, portanto você não poderá alterar seus elementos após a construção. Você pode experimentar esses resources usando o Acesso Antecipado do JDK 9 .

Se você precisar adicionar apenas um valor ao mapa, poderá usar Collections.singletonMap :

 Map map = Collections.singletonMap(key, value) 

Bem … eu gosto de enums;)

 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 MAP = Stream.of( values() ) .collect( Collectors.toMap( e -> e.value, e -> e.name ) ); } 

O segundo método poderia invocar methods protegidos, se necessário. Isso pode ser útil para inicializar classs que são imutáveis ​​após a construção.