Comment initialiser directement un HashMap (de façon littérale)?

1096

Existe-t-il un moyen d'initialiser un HashMap Java comme celui-ci?:

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

Quelle serait la syntaxe correcte? Je n'ai rien trouvé à ce sujet. Est-ce possible? Je recherche le moyen le plus court / le plus rapide de mettre des valeurs "finales / statiques" dans une carte qui ne changent jamais et qui sont connues à l'avance lors de la création de la carte.

jens
la source
Étroitement liés: stackoverflow.com/questions/507602/… (Les deux questions concernent l'initialisation d'une carte constante avec des valeurs finales statiques.)
Jonik
Dans Java 9: techiedelight.com/initialize-map-java9
Kamil Tomasz Jarmusik
Si vous utilisez apache.commons.collections, vous pouvez utiliser commons.apache.org/proper/commons-collections/javadocs/…
ax.

Réponses:

1347

Toutes les versions

Au cas où vous auriez besoin d'une seule entrée: Oui Collections.singletonMap("key", "value").

Pour Java version 9 ou supérieure:

Oui, c'est possible maintenant. Dans Java 9, quelques méthodes d'usine ont été ajoutées pour simplifier la création de cartes:

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

Dans l'exemple ci-dessus les deux testet test2seront les mêmes, juste avec différentes manières d'exprimer la carte. La Map.ofméthode est définie pour jusqu'à dix éléments dans la carte, tandis que la Map.ofEntriesméthode n'aura pas une telle limite.

Notez que dans ce cas, la carte résultante sera une carte immuable. Si vous souhaitez que la carte soit modifiable, vous pouvez la recopier, par exemple en utilisantmutableMap = new HashMap<>(Map.of("a", "b"));

(Voir aussi JEP 269 et Javadoc )

Jusqu'à Java version 8:

Non, vous devrez ajouter tous les éléments manuellement. Vous pouvez utiliser un initialiseur dans une sous-classe anonyme pour raccourcir un peu la syntaxe:

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

Cependant, la sous-classe anonyme peut introduire un comportement indésirable dans certains cas. Cela comprend par exemple:

  • Il génère une classe supplémentaire qui augmente la consommation de mémoire, la consommation d'espace disque et le temps de démarrage
  • Dans le cas d'une méthode non statique: elle contient une référence à l'objet auquel la méthode de création a été appelée. Cela signifie que l'objet de la classe externe ne peut pas être récupéré pendant que l'objet de carte créé est toujours référencé, bloquant ainsi la mémoire supplémentaire

L'utilisation d'une fonction d'initialisation vous permettra également de générer une carte dans un initialiseur, mais évite les effets secondaires désagréables:

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;
}
yankee
la source
3
Cela ne fonctionnera pas si vous voulez parapher les éléments dans une fonction ...
Michael
9
@Michael: Eh bien oui, si vous voulez utiliser une fonction, vous ne pouvez pas utiliser une non-fonction. Mais pourquoi voulez-vous?
yankee
6
et pour les cas où vous avez besoin d'une carte avec une seule entrée, il y a Collections.singletonMap():)
skwisgaar
3
Maintenant que Java 9 stable est sorti, je préfère ce lien pour Javadoc . Et +1 car une dépendance de moins!
Franklin Yu
3
Où Java 9 est-il entrydocumenté?
nobar
1031

C'est une façon.

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

Cependant, vous devez être prudent et vous assurer que vous comprenez le code ci-dessus (il crée une nouvelle classe qui hérite de HashMap). Par conséquent, vous devriez en savoir plus ici: http://www.c2.com/cgi/wiki?DoubleBraceInitialization , ou simplement utiliser Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
gregory561
la source
72
Cela fonctionne mais c'est moche et a des effets secondaires invisibles que l'utilisateur doit comprendre avant de le faire - par exemple, générer une classe anonyme entière sur place.
jprete
96
oui, c'est ainsi que j'ai écrit sur la prudence et que j'ai donné un lien vers la description.
gregory561
6
Excellent lien. La référence dans ce lien à GreencoddsTenthRuleOfProgramming vaut la peine d'être lue.
michaelok
19
pouvez-vous ajouter "as ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" car la méthode "of" est limitée à 5 paires au maximum?
kommradHomer
342

Si vous permettez libs 3ème partie, vous pouvez utiliser goyave de ImmutableMap pour atteindre la brièveté comme littérale:

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

Cela fonctionne jusqu'à 5 paires clé / valeur , sinon vous pouvez utiliser son générateur :

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


  • A noter que de Goyave ImmutableMap de mise en œuvre diffère de Java de HashMap de mise en œuvre (notamment , il est immuable et ne permet pas les clés / valeurs nulles)
  • pour plus d'informations, consultez l'article du guide de l'utilisateur de Guava sur ses types de collection immuables
Jens Hoffmann
la source
26
De plus, la goyave a ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius
17
ImmutableMap n'est pas identique à un HashMap, car il échouera sur les valeurs nulles, contrairement à la carte HashMap.
Gewthen
2
Juste pour aider ceux qui pourraient être confrontés à ce problème. Vous devez taper le générateur pour en faire une Map <String, String>, comme ceci: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Thiago
c'est génial Jens!
gaurav
105

Il n'y a aucun moyen direct de le faire - Java n'a pas de littéraux de carte (pourtant - je pense qu'ils ont été proposés pour Java 8).

Certaines personnes aiment ça:

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

Cela crée une sous-classe anonyme de HashMap, dont l'initialiseur d'instance place ces valeurs. (Soit dit en passant, une carte ne peut pas contenir deux fois la même valeur, votre deuxième put remplacera la première. J'utiliserai des valeurs différentes pour les exemples suivants.)

La façon normale serait la suivante (pour une variable locale):

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

Si votre testcarte est une variable d'instance, placez l'initialisation dans un constructeur ou un initialiseur d'instance:

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

Si votre testcarte est une variable de classe, placez l'initialisation dans un initialiseur statique:

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

Si vous souhaitez que votre carte ne change jamais, vous devez, après l'initialisation, envelopper votre carte par Collections.unmodifiableMap(...). Vous pouvez également le faire dans un initialiseur statique:

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

(Je ne sais pas si vous pouvez maintenant rendre la version testfinale ... essayez-la et faites un rapport ici.)

Paŭlo Ebermann
la source
61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};
Grenouille hirsute
la source
Simple et précis. Je pense que cela avec une longue section de commentaires serait la meilleure réponse.
ooolala
15
Il y a cependant des implications de mémoire qui doivent être notées. blog.jooq.org/2014/12/08/…
Amalgovinus
1
@Amalgovinus Fondamentalement, en créant une nouvelle sous-classe, vous codez en dur les arguments de type à partir de HashMapcette sous-classe. Cela ne peut fonctionner que si vous les fournissez réellement. (Avec un nouveau HashMap (vide), les arguments de type ne sont pas pertinents.)
Paŭlo Ebermann
1
J'aime sa propreté, mais cela crée une classe anonyme inutile et a les problèmes décrits ici: c2.com/cgi/wiki?DoubleBraceInitialization
udachny
1
@hello_its_me: Parce que c'est la même chose que stackoverflow.com/a/6802512/1386911 , juste la mise en forme différente. Et dans ce cas, cette mise en forme étendue n'a aucune valeur supplémentaire en plus du format compact pour la lisibilité.
Daniel Hári
44

Une alternative, en utilisant des classes et varargs Java 7 simples: créez une classe HashMapBuilderavec cette méthode:

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

Utilisez la méthode comme ceci:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
Aerthel
la source
J'ai écrit une réponse inspirée de la vôtre: stackoverflow.com/questions/507602/…
GeroldBroser réintègre Monica le
1
Une autre solution avec Apache Utils qui n'est jamais mentionnée mais qui est lisible, en utilisant les versions précédentes de Java: MapUtils.putAll (new HashMap <String, String> (), new Object [] {"My key", "my value", ...
Rolintocour
4

tl; dr

Utilisez des Map.of…méthodes dans Java 9 et versions ultérieures.

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

Map.of

Java 9 a ajouté une série de Map.ofméthodes statiques pour faire exactement ce que vous voulez: Instancier une immuable en Maputilisant une syntaxe littérale .

La carte (une collection d'entrées) est immuable, vous ne pouvez donc pas ajouter ou supprimer d'entrées après l'instanciation. De plus, la clé et la valeur de chaque entrée sont immuables et ne peuvent pas être modifiées. Voir le Javadoc pour d'autres règles, telles qu'aucune valeur NULL autorisée, aucune clé en double autorisée et l'ordre d'itération des mappages est arbitraire.

Examinons ces méthodes, en utilisant des exemples de données pour une carte du jour de la semaine pour une personne qui, selon nous, travaillera ce jour-là.

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

Map.of()

Map.ofcrée un vide Map. Non modifiable, vous ne pouvez donc pas ajouter d'entrées. Voici un exemple d'une telle carte, vide sans entrées.

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

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)sont plusieurs méthodes qui prennent 1 à 10 paires clé-valeur. Voici un exemple de deux entrées.

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

weekendWorker.toString (): {DIMANCHE = Personne {nom = 'Bob'}, SAMEDI = Personne {nom = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )prend n'importe quel nombre d'objets implémentant l' Map.Entryinterface. Java regroupe deux classes implémentant cette interface, l'une mutable, l'autre immuable: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Mais nous n'avons pas besoin de spécifier une classe concrète. Nous avons simplement besoin d'appeler la Map.entry( k , v )méthode, de passer notre clé et notre valeur, et nous récupérons un objet d'une certaine Map.Entryinterface d' implémentation de 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 )
);

weekendWorker.toString (): {WEDNESDAY = Person {name = 'Bob'}, TUESDAY = Person {name = 'Bob'}, THURSDAY = Person {name = 'Carol'}, FRIDAY = Person {name = 'Carol'} , LUNDI = Personne {name = 'Alice'}}

Map.copyOf

Java 10 a ajouté la méthode Map.copyOf. Passez une carte existante, récupérez une copie immuable de cette carte.

Remarques

Notez que l'ordre des itérateurs des cartes produites via neMap.of sont pas garanti. Les entrées ont un ordre arbitraire. N'écrivez pas de code basé sur la commande vue, car la documentation prévient que la commande est susceptible de changer.

Notez que toutes ces Map.of…méthodes renvoient un Mapd' une classe non spécifiée . La classe concrète sous-jacente peut même varier d'une version de Java à une autre. Cet anonymat permet à Java de choisir parmi diverses implémentations, quelle que soit celle qui correspond de manière optimale à vos données particulières. Par exemple, si vos clés proviennent d'une énumération , Java peut utiliser un EnumMapsous les couvertures.

Basil Bourque
la source
1

Vous pouvez éventuellement créer votre propre Map.ofméthode (qui n'est disponible que dans Java 9 et supérieur) facilement de 2 manières simples

Faites-le avec une quantité définie de paramètres

Exemple

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

Faites-le en utilisant une liste

Vous pouvez également faire cela en utilisant une liste, au lieu de faire beaucoup de méthodes pour un certain ensemble de paramètres.

Exemple

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

Remarque Il n'est pas recommandé de l'utiliser pour tout car cela crée une classe anonyme à chaque fois que vous l'utilisez.

NotNV6
la source
1

JAVA 8

Dans plain java 8, vous avez également la possibilité d'utiliser Streams/Collectorspour faire le travail.

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

Cela a l'avantage de ne pas créer de classe anonyme.

Notez que les importations sont:

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

Bien sûr, comme indiqué dans d'autres réponses, dans java 9 et versions ultérieures, vous avez des moyens plus simples de faire de même.

Johnny Willer
la source
0

Malheureusement, l'utilisation de varargs si le type des clés et des valeurs n'est pas le même n'est pas très raisonnable car vous devrez utiliser Object...et perdre complètement la sécurité du type. Si vous voulez toujours créer par exemple un Map<String, String>, a toMap(String... args)serait bien sûr possible, mais pas très joli car il serait facile de mélanger les clés et les valeurs, et un nombre impair d'arguments ne serait pas valide.

Vous pouvez créer une sous-classe de HashMap qui a une méthode chaînable comme

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

et l'utiliser comme new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Une autre approche consiste à utiliser le modèle de générateur commun:

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

et l'utiliser comme new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Cependant, la solution que j'ai utilisée de temps en temps utilise des varargs et la 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");

La verbosité de Pair.create()me dérange un peu, mais cela fonctionne très bien. Si cela ne vous dérange pas les importations statiques, vous pouvez bien sûr créer une aide:

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

(Au lieu de Pairl'imaginer Map.Entry, mais comme il s'agit d'une interface, elle nécessite une classe d'implémentation et / ou une méthode d'usine d'assistance. Elle n'est pas non plus immuable et contient une autre logique non utile pour cette tâche.)

JHH
la source
0

Vous pouvez utiliser Streams In Java 8 (c'est un exemple de Set):

@Test
public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
    Set<String> countries = Stream.of("India", "USSR", "USA")
      .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

    assertTrue(countries.contains("India"));
}

Réf: https://www.baeldung.com/java-double-brace-initialization

Robocide
la source
0

Si vous devez placer une seule paire clé-valeur, vous pouvez utiliser Collections.singletonMap (clé, valeur);

Balakrishna Kudikala
la source
1
le code de mise en forme rend le post beaucoup plus lisible
Renato