Como posso inicializar um mapa estático?

1131

Como você inicializaria uma estática Mapem Java?

Método 1: inicializador estático
Método 2: inicializador de instância (subclasse anônima) ou algum outro método?

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

Aqui está um exemplo que ilustra os dois métodos:

import java.util.HashMap;
import java.util.Map;

public class Test {
    private static final Map<Integer, String> myMap = new HashMap<>();
    static {
        myMap.put(1, "one");
        myMap.put(2, "two");
    }

    private static final Map<Integer, String> myMap2 = new HashMap<>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}
dogbane
fonte
2
Para inicializar um mapa no Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal
2
Por favor, nunca use a inicialização entre chaves - é um hack e uma maneira fácil de vazar memória e causar outros problemas.
dimo414
Java 9? Se as entradas count <= 10 utilização Map.ofoutra coisa Map.ofEntries, verifique stackoverflow.com/a/37384773/1216775
akhil_mittal

Respostas:

1106

O inicializador de instância é apenas açúcar sintático, neste caso, certo? Não vejo por que você precisa de uma classe anônima extra apenas para inicializar. E não funcionará se a classe 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<Integer, String> myMap;
    static {
        Map<Integer, String> aMap = ....;
        aMap.put(1, "one");
        aMap.put(2, "two");
        myMap = Collections.unmodifiableMap(aMap);
    }
}
Variável miserável
fonte
10
Este é o idioma que eu uso há anos e nunca tive ninguém de olho nele. Eu faço o mesmo também para conjuntos e listas constantes não modificáveis.
Jsonmp85
3
Como eu lidaria com um HashMap <String, String> com uma chave String. O objeto Map não me permite ter uma chave String, então não posso usar unmodifiableMap (). Eu acho que a conversão para um HashMap também derrotaria o objetivo. Alguma ideia?
Lucas
30
@ Lucas, duvido seriamente que o Android tenha essa limitação. Não faz nenhum sentido. Uma pesquisa rápida encontrou essa pergunta aqui (e muitas outras), o que parece sugerir que você pode usar uma chave String para um objeto Map no Android.
mluisbrown
11
Para que ninguém mais se incomode em investigar, posso confirmar que não há problema em usar uma chave String para um objeto Map no Android.
Jordan
11
Jordan: agora é um tópico antigo, mas suspeito que o @Luke estava tentando usar uma string como chave em um mapa que tinha um tipo de chave diferente, por exemplo, Map <Integer, String>.
Variável miserável
445

Eu gosto da maneira Guava de inicializar um mapa estático e imutável:

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

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

Se você deseja que o mapa tenha mais de 5 entradas, não é mais possível usá-lo ImmutableMap.of(). Em vez disso, tente ImmutableMap.builder()ao longo destas linhas:

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

Para saber mais sobre os benefícios dos utilitários de coleção imutável do Guava, consulte Coleções imutáveis ​​explicadas no Guia do Usuário do Guava .

(Um subconjunto de) Goiaba costumava ser chamado de Coleções do Google . Se você não estiver usando esta biblioteca no seu projeto Java ainda, eu fortemente recomendo tentar it out! A goiaba rapidamente se tornou uma das bibliotecas de terceiros gratuitas mais populares e úteis para Java, conforme concordam os colegas usuários do SO . (Se você é novo, existem excelentes recursos de aprendizado por trás desse link.)


Atualização (2015) : Quanto ao Java 8 , bem, eu ainda usaria a abordagem Guava porque é muito mais limpa do que qualquer outra coisa. Se você não quer a dependência do Guava, considere um método init simples e antigo . O hack com matriz bidimensional e a API do Stream é muito feio se você me perguntar e fica mais feio se você precisar criar um mapa cujas chaves e valores não sejam do mesmo tipo (como Map<Integer, String>na pergunta).

Quanto ao futuro do Guava em geral, no que diz respeito ao Java 8, Louis Wasserman disse isso em 2014 e [ atualização ] em 2016 foi anunciado que o Guava 21 exigirá e dará suporte adequado ao Java 8 .


Atualização (2016) : Como Tagir Valeev aponta , o Java 9 finalmente tornará isso mais limpo usando nada além de JDK puro, adicionando métodos de fábrica convenientes para coleções:

static final Map<Integer, String> MY_MAP = Map.of(
    1, "one", 
    2, "two"
);
Jonik
fonte
21
Parece que nossos colegas administradores de SO excluíram a venerável "Bibliotecas Java gratuitas de terceiros mais úteis" à qual vinculei. :( Droga, eles.
Jonik
2
Concordo que esta é a melhor maneira de inicializar um mapa constante. Não apenas mais legível, como também Collections.unmodifiableMap retorna uma visualização somente leitura do mapa subjacente (que ainda pode ser modificado).
Crunchdog
11
Agora posso ver perguntas excluídas (com 10k + rep), então aqui está uma cópia das 'Bibliotecas Java de terceiros gratuitas mais úteis' . É apenas a primeira página, mas pelo menos você pode encontrar os recursos do Goiaba mencionados acima.
Jonik
2
Eu realmente prefiro essa abordagem, embora seja benéfico saber fazê-lo sem dependências extras.
Chave
2
PEC 186 ainda não está fechada, por isso pode introduzir novas funcionalidades relacionadas com literais de coleta
cybersoft
182

Eu usaria:

public class Test {
    private static final Map<Integer, String> MY_MAP = createMap();

    private static Map<Integer, String> createMap() {
        Map<Integer, String> result = new HashMap<>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  1. evita uma classe anônima, que eu pessoalmente considero um estilo ruim, e evito
  2. torna a criação do mapa mais explícita
  3. torna o mapa inalterável
  4. como MY_MAP é constante, eu o chamaria de constante
Peter Štibraný
fonte
3
Das opções JDK puras (sem bibliotecas), eu gosto mais disso, porque a definição do mapa está claramente vinculada à sua inicialização. Também concordou em nomeação constante.
Jonik
Nunca me ocorreu que você pudesse fazer isso.
romulusnr 21/04
181

O Java 5 fornece essa sintaxe mais compacta:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};
Chris Noe
fonte
46
Essa técnica é chamada de inicialização entre chaves: stackoverflow.com/questions/1372113/… Não é uma sintaxe especial do Java 5, é apenas um truque com uma classe anônima com um inicializador de instância.
Jesper
13
Pergunta rápida sobre a inicialização de chaves duplas: Ao fazer isso, o Eclipse emite um Aviso sobre um ID de Série ausente. Por um lado, não vejo por que um ID de série seria necessário nesse caso específico, mas, por outro lado, geralmente não gosto de suprimir avisos. Quais são seus pensamentos sobre isso?
N
8
@nbarraille Isso é porque HashMap implements Serializable. Como você realmente cria uma subclasse do HashMap usando esse "truque", cria implicitamente uma classe Serializable. E para isso você deve fornecer um serialUID.
ninguém
5
Double brace initialization can cause memory leaks when used from a non-static context, because the anonymous class created will maintain a reference to the surrounding object. It has worse performance than regular initialization because of the additional class loading required. It can cause equals() comparisons to fail, if the equals() method does not accept subclasses as parameter. And finally, pre Java 9 it cannot be combined with the diamond operator, because that cannot be used with anonymous classes.- IntelliJ
Mark Jeronimus
3
@ MarkJeronimus - O uso sugerido é um contexto estático. O desempenho pode ser pior, mas não visivelmente quando se lida com um número presumivelmente pequeno de mapas definidos estaticamente. HashMap.equalsé definido AbstractMape funciona em qualquer subclasse de Map, portanto não é uma preocupação aqui. A coisa do operador de diamante é irritante, mas, como mencionado, agora foi resolvido.
Jules
95

Uma vantagem do segundo método é que você pode envolvê-lo Collections.unmodifiableMap()para garantir que nada atualize a coleção posteriormente:

private static final Map<Integer, String> CONSTANT_MAP = 
    Collections.unmodifiableMap(new HashMap<Integer, String>() {{ 
        put(1, "one");
        put(2, "two");
    }});

 // later on...

 CONSTANT_MAP.put(3, "three"); // going to throw an exception!
Programador foragido
fonte
3
Você não pode fazer isso facilmente no primeiro método, movendo o novo operador para o bloco estático {} e envolvendo-o?
305 Patrick Patrick
2
Eu moveria a chamada do construtor para a estática inicializada de qualquer maneira. Qualquer outra coisa parece estranha.
Tom Hawtin - tackline
2
Alguma idéia de qual desempenho pode haver ao usar uma classe anônima em vez de uma classe concreta?
Kip
62

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

private static final Map<String, String> 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<Integer, String>como na pergunta, você precisa de algo como isto:

static final Map<Integer, String> 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 melhorada para o tipo misto do i_am_zero que usa um fluxo de new SimpleEntry<>(k, v)chamadas. Confira essa resposta: https://stackoverflow.com/a/37384773/3950982

Luke Hutchison
fonte
7
Tomei a liberdade de adicionar uma versão equivalente à pergunta e a outras respostas: inicie um mapa cujas chaves e valores sejam de tipos diferentes (por String[][]isso não Object[][]é necessário , é necessário). IMHO, essa abordagem é feia (ainda mais com os elencos) e difícil de lembrar; eu não usaria isso sozinho.
Jonik
57

Map.of em Java 9+

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

Veja JEP 269 para detalhes. O JDK 9 atingiu a disponibilidade geral em setembro de 2017.

Tagir Valeev
fonte
7
Ou se você quiser mais de 10 pares de valor-chave, você pode usarMap.ofEntries
ZhekaKozlov
8
Isso é limpo e tudo, até você perceber como foi implementado
meados
Ugh, isso é tão triste - parece que ele suporta apenas 10 entradas, após o qual você precisa usar ofEntries. Coxo.
Somaiah Kumbera
2
A limpeza da implementação no JDK não deve importar desde que funcione e satisfaça o contrato. Como qualquer caixa preta, detalhes de implementação sempre pode ser corrigido no futuro, se realmente necessário ...
vikingsteve
@mid Essa é a única maneira segura de fazer isso em Java.
Luke Hutchison
44

Java 9

Podemos usar Map.ofEntries, chamando Map.entry( k , v )para criar cada entrada.

import static java.util.Map.entry;
private static final Map<Integer,String> map = Map.ofEntries(
        entry(1, "one"),
        entry(2, "two"),
        entry(3, "three"),
        entry(4, "four"),
        entry(5, "five"),
        entry(6, "six"),
        entry(7, "seven"),
        entry(8, "eight"),
        entry(9, "nine"),
        entry(10, "ten"));

Também podemos usar Map.ofcomo sugerido por Tagir em sua resposta aqui, mas não podemos ter mais de 10 entradas usando Map.of.

Java 8 (solução pura)

Podemos criar um fluxo de entradas do mapa. Já temos duas implementações Entryem java.util.AbstractMapque são SimpleEntry e SimpleImmutableEntry . Neste exemplo, podemos fazer uso do ex como:

import java.util.AbstractMap.*;
private static final Map<Integer, String> 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));
akhil_mittal
fonte
2
O new SimpleEntry<>()caminho é muito menos legível que estático put(): /
Danon
32

Com o Eclipse Collections , todos os seguintes itens 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<Integer, String> MAP =
        Maps.mutable.with(1, "one", 2, "two");

    private static final MutableMap<Integer, String> MUTABLE_MAP =
       Maps.mutable.with(1, "one", 2, "two");


    private static final MutableMap<Integer, String> UNMODIFIABLE_MAP =
        Maps.mutable.with(1, "one", 2, "two").asUnmodifiable();


    private static final MutableMap<Integer, String> SYNCHRONIZED_MAP =
        Maps.mutable.with(1, "one", 2, "two").asSynchronized();


    private static final ImmutableMap<Integer, String> IMMUTABLE_MAP =
        Maps.mutable.with(1, "one", 2, "two").toImmutable();


    private static final ImmutableMap<Integer, String> IMMUTABLE_MAP2 =
        Maps.immutable.with(1, "one", 2, "two");
}

Você também pode inicializar estaticamente mapas primitivos com o Eclipse Collections.

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<String> MUTABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two");

    private static final MutableIntObjectMap<String> UNMODIFIABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .asUnmodifiable();

    private static final MutableIntObjectMap<String> SYNCHRONIZED_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .asSynchronized();

    private static final ImmutableIntObjectMap<String> IMMUTABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .toImmutable();

    private static final ImmutableIntObjectMap<String> IMMUTABLE_INT_OBJ_MAP2 =
            IntObjectMaps.immutable.<String>empty()
                    .newWithKeyValue(1, "one")
                    .newWithKeyValue(2, "two");
} 

Nota: Eu sou um confirmador das Coleções Eclipse

Donald Raab
fonte
1
Eu realmente gostaria que o Eclipse Collections fosse a biblioteca de coleções padrão para Java. Gosto muito mais do que Guava + JCL.
Kenny Cason
29

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

private static final Map<Integer, String> MY_MAP;
static
{
    Map<Integer, String>tempMap = new HashMap<Integer, String>();
    tempMap.put(1, "one");
    tempMap.put(2, "two");
    MY_MAP = Collections.unmodifiableMap(tempMap);
}
eljenso
fonte
1
Em que situação você usaria uma subclasse anônima para inicializar um hashmap?
224 dogbane
6
Nunca inicializar uma coleção.
eljenso
Você poderia explicar por que usar um inicializador estático é uma escolha melhor do que criar uma subclasse anônima?
Leba-lev
3
@rookie Existem várias razões dadas em outras respostas que favorecem o init estático. O objetivo aqui é inicializar, então, por que trazer a subclasse, exceto talvez salvar algumas teclas digitadas? (Se você deseja economizar com o pressionamento de teclas, o Java definitivamente não é uma boa escolha como linguagem de programação.) Uma regra prática que uso ao programar em Java é: subclasse o mínimo possível (e nunca quando for razoavelmente evitada).
eljenso
@eljenso - a razão de eu geralmente favorecer a sintaxe da subclasse é que ela coloca a inicialização em linha, onde ela pertence . Uma segunda melhor opção é chamar um método estático que retorne o mapa inicializado. Mas tenho medo de olhar para o seu código e ter que gastar alguns segundos para descobrir de onde vem o MY_MAP, e é dessa hora que não quero perder. Qualquer melhoria na legibilidade é um bônus, e as consequências no desempenho são mínimas, por isso parece a melhor opção para mim.
Jules
18

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, além de fornecer coleções imutáveis.

Atualização: Esta biblioteca agora se chama Guava .

Kaarel
fonte
17

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

public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
    {
        put(1, "some value");
                    //rest of code here
    }
});
Shushant
fonte
12
public class Test {
    private static final Map<Integer, String> myMap;
    static {
        Map<Integer, String> aMap = ....;
        aMap.put(1, "one");
        aMap.put(2, "two");
        myMap = Collections.unmodifiableMap(aMap);
    }
}

Se declararmos mais de uma constante, esse código será gravado em bloco estático e é difícil de manter no futuro. Portanto, é melhor usar classe 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 o uso de unmodifiableMap para constantes; caso contrário, não pode ser tratado como constante.

Leninkumar Koppoju
fonte
10

Eu poderia sugerir fortemente o estilo "inicialização entre chaves" sobre o estilo de bloco estático.

Alguém pode comentar que não gosta de classe anônima, sobrecarga, desempenho etc.

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

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

Além disso, se você conhece o GC da classe anônima, sempre pode convertê-lo em um HashMap normal usando new HashMap(Map map).

Você pode fazer isso até enfrentar outro problema. Se o fizer, você deve usar outro estilo de codificação (por exemplo, não estático, classe de fábrica) para ele.

Dennis C
fonte
8

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

Por exemplo, para criar um mapa de cores:

Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
     {"RED", "#FF0000"},
     {"GREEN", "#00FF00"},
     {"BLUE", "#0000FF"}
 });
agad
fonte
Eu incluo o Apache Commons em todas as compilações, portanto, na infeliz ausência de um método Arrays.asMap( ... )no Java comum, acho que essa é a melhor solução. Reinventar a roda geralmente é bobagem. Uma desvantagem muito pequena é que, com os genéricos, será necessária uma conversão desmarcada.
mike roedor
A versão do @mikerodent 4.1 é genérica: public static <K, V> Map <K, V> putAll (mapa final <K, V> map, final Object [] array)
agad
Tx ... sim, eu estou usando 4.1, mas ainda preciso SuppressWarnings( unchecked )no Eclipse com uma linha como:Map<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
mike
@mikerodent não é por causa do objeto [] [] ? Consulte atualização não juramentada - Não tenho nenhum aviso no Eclipse.
agad 28/11
Que estranho ... mesmo quando vou String[][], recebo o "aviso"! E, claro, isso só funciona se você Ke Va mesma classe. Suponho que você não tenha (compreensivelmente) definido "conversão desmarcada" como "Ignorar" em sua configuração do Eclipse?
mike roedor
7

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

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

É muito compacto e ignora valores perdidos (ou seja, uma chave final sem valor).

Uso:

Map<String, String> one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal");
Map<String, Object> two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2));
neu242
fonte
7

Se você deseja um mapa não modificável, finalmente o java 9 adicionou um método legal de fábrica para fazer ofa Mapinterface. Um método semelhante é adicionado ao Set, List também.

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

Bharanidharan K
fonte
6

Eu prefiro usar um inicializador estático para evitar a geração de classes anônimas (que não teriam mais nenhum objetivo), portanto, listarei dicas de inicialização com um inicializador estático. Todas as soluções / dicas listadas são de tipo seguro.

Nota: A pergunta não diz nada sobre como tornar o mapa inalterável, então deixarei isso de fora, mas sei que isso pode ser feito facilmente Collections.unmodifiableMap(map).

Primeira dica

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

private static final Map<Integer, String> myMap = new HashMap<>();
static {
    final Map<Integer, String> 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 entradas; você também pode tornar público esse método auxiliar se desejar:

private static final Map<Integer, String> 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, porque apenas pode adicionar elementos myMap2. Para torná-lo reutilizável, poderíamos tornar o mapa em si um parâmetro do método auxiliar, mas o código de inicialização não seria mais curto.

Terceira dica

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

public class Test {
    private static final Map<Integer, String> 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<K, V> {
    private final Map<K, V> m;

    public B(Map<K, V> m) {
        this.m = m;
    }

    public B<K, V> p(K k, V v) {
        m.put(k, v);
        return this; // Return this for chaining
    }
}
icza
fonte
5

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

Dito isto, desde que você esteja ciente, essa abordagem é adequada. Eu o uso na maioria das vezes para inicializar todos os tipos de coleções de maneira concisa.

Edição: apontou corretamente nos comentários que esta é uma classe estática. Obviamente, não li isso de perto. No entanto os meus comentários que ainda se aplicam a classes internas anônimas.

Brian Agnew
fonte
3
Nesse caso em particular, é estático, portanto, nenhuma instância externa.
Tom Hawtin - tackline 03/02/09
Indiscutivelmente XStream não deve estar tentando coisas serialize como este (de estática Por que você precisa para serializar uma variável estática.?)
jasonmp85
5

Se você quiser algo conciso e relativamente seguro, basta mudar a verificação do tipo em tempo de compilação para o tempo de execução:

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

Esta implementação deve detectar quaisquer erros:

import java.util.HashMap;

public abstract class MapUtils
{
    private MapUtils() { }

    public static <K, V> HashMap<K, V> unmodifiableMap(
            Class<? extends K> keyClazz,
            Class<? extends V> valClazz,
            Object...keyValues)
    {
        return Collections.<K, V>unmodifiableMap(makeMap(
            keyClazz,
            valClazz,
            keyValues));
    }

    public static <K, V> HashMap<K, V> 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<K, V> result = new HashMap<K, V>(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> 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);
        }
    }
}
Philip Guin
fonte
4

Com o Java 8, passei a usar o seguinte padrão:

private static final Map<String, Integer> 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 de java.util
  • é seguro e acomoda facilmente diferentes tipos de chave e valor.
zrvan
fonte
se necessário, pode-se usar a toMapassinatura incluindo um fornecedor de mapas para especificar o tipo de mapa.
Zrvan
4

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

Map<K, V> map = Collections.singletonMap(key, value)
Stromata
fonte
4

Você pode usar StickyMape MapEntryde Cactoos :

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

Sua segunda abordagem (inicialização do Double Brace) é considerada um anti-padrão , então eu usaria a primeira abordagem.

Outra maneira fácil de inicializar um mapa estático é usando esta função de utilitário:

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

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

R. Oosterholt
fonte
3

Não gosto da sintaxe do inicializador estático e não estou convencido de subclasses anônimas. Geralmente, concordo com todos os contras do uso de inicializadores estáticos e com todos os contras do uso de subclasses anônimas mencionadas nas respostas anteriores. Por outro lado, os profissionais apresentados nesses posts não são suficientes para mim. Eu prefiro usar o método de inicialização estática:

public class MyClass {
    private static final Map<Integer, String> myMap = prepareMap();

    private static Map<Integer, String> prepareMap() {
        Map<Integer, String> hashMap = new HashMap<>();
        hashMap.put(1, "one");
        hashMap.put(2, "two");

        return hashMap;
    }
}
Stanisław Borowy
fonte
3

Eu não vi a abordagem que eu uso (e passei a gostar) postada em nenhuma resposta, então aqui está:

Não gosto de usar inicializadores estáticos porque são desajeitados e não gosto de classes anônimas porque estão criando uma nova classe para cada instância.

em vez disso, prefiro uma inicialização assim:

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

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

 public static <K,V> Map<K,V> map(Map.Entry<K, ? extends V>... entries)
 public static <K,V> Map.Entry<K,V> entry(K key, V val)

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

Achei útil fornecer métodos estáticos semelhantes para as outras coleções (lista, conjunto, SortedSet, SortedMap, etc.)

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

Josh
fonte
3

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

Felizmente, é possível aproximar o comportamento dos literais de mapas em Java usando métodos de fábrica .

Por exemplo:

public class LiteralMapFactory {

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

Resultado:

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

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

nazar_art
fonte
2

O JEP 269 fornece alguns métodos de fábrica convenientes para a API de coleções. Esses métodos de fábrica não estão na versão Java atual, que é 8, mas estão planejados para a liberação do Java 9.

Pois Mapexistem dois métodos de fábrica: ofe ofEntries. Usando of, você pode passar pares alternados de chave / valor. Por exemplo, para criar um Maplike {age: 27, major: cs}:

Map<String, Object> info = Map.of("age", 27, "major", "cs");

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

Map<String, Object> info = Map.ofEntries(
                Map.entry("age", 27),
                Map.entry("major", "cs")
);

Ambos ofe ofEntriesretornará uma imutável Map, então você não pode mudar os seus elementos após a construção. Você pode experimentar esses recursos usando o JDK 9 Early Access .

Ali Dehghani
fonte
2

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

Eu li as respostas e decidi escrever meu próprio construtor de mapas. Sinta-se livre para copiar e colar e aproveitar.

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

/**
 * A tool for easy creation of a map. Code example:<br/>
 * {@code MapBuilder.of("name", "Forrest").and("surname", "Gump").build()}
 * @param <K> key type (inferred by constructor)
 * @param <V> value type (inferred by constructor)
 * @author Vlasec (for http://stackoverflow.com/a/30345279/1977151)
 */
public class MapBuilder <K, V> {
    private Map<K, V> map = new HashMap<>();

    /** Constructor that also enters the first entry. */
    private MapBuilder(K key, V value) {
        and(key, value);
    }

    /** Factory method that creates the builder and enters the first entry. */
    public static <A, B> MapBuilder<A, B> mapOf(A key, B value) {
        return new MapBuilder<>(key, value);
    }

    /** Puts the key-value pair to the map and returns itself for method chaining */
    public MapBuilder<K, V> and(K key, V value) {
        map.put(key, value);
        return this;
    }

    /**
     * If no reference to builder is kept and both the key and value types are immutable,
     * the resulting map is immutable.
     * @return contents of MapBuilder as an unmodifiable map.
     */
    public Map<K, V> build() {
        return Collections.unmodifiableMap(map);
    }
}

EDIT: Ultimamente, continuo encontrando métodos estáticos públicos com ofbastante frequência e meio que gosto disso. Eu o adicionei no código e tornei o construtor privado, alternando para o padrão de método de fábrica estático.

EDIT2: Ainda mais recentemente, não gosto mais do método estático chamado of, pois parece muito ruim ao usar importações estáticas. Eu o renomei para mapOftorná-lo mais adequado para importações estáticas.

Vlasec
fonte