web-dev-qa-db-fra.com

Comment puis-je initialiser une carte statique?

Comment initialiser une Map statique en Java?

Première méthode: initialiseur statique 
Méthode deux: initialiseur d'instance (sous-classe anonyme) Ou Une autre méthode?

Quels sont les avantages et les inconvénients de chacun?

Voici un exemple illustrant les deux méthodes:

import Java.util.HashMap;
import Java.util.Map;

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

    private static final Map<Integer, String> myMap2 = new HashMap<Integer, String>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}
997
dogbane

L'initialisateur d'instance est simplement du sucre syntaxique dans ce cas, non? Je ne vois pas pourquoi vous avez besoin d'une classe anonyme supplémentaire juste pour initialiser. Et cela ne fonctionnera pas si la classe en cours de création est finale.

Vous pouvez également créer une carte immuable à l’aide d’un initialiseur statique:

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);
    }
}
1008
Miserable Variable

J'aime la goyave moyen d'initialiser une carte statique et immuable:

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

Comme vous pouvez le constater, il est très concis (en raison des méthodes pratiques en usine décrites dans ImmutableMap ).

Si vous souhaitez que la carte contienne plus de 5 entrées, vous ne pouvez plus utiliser ImmutableMap.of(). Au lieu de cela, essayez ImmutableMap.builder() selon ces lignes:

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

Pour en savoir plus sur les avantages des utilitaires de collection immuables de Guava, voir Collections immuables expliquées dans le Guide de l'utilisateur de Guava .

(Un sous-ensemble de) Goyave s'appelait _ {Google Collections} _. Si vous n'utilisez pas encore cette bibliothèque dans votre projet Java, je vivement vous recommandons de l'essayer! Guava est rapidement devenu l’une des librairies tierces gratuites les plus populaires et les plus utiles pour Java, comme l’affirment les autres utilisateurs de SO . (Si vous débutez dans ce domaine, d’excellentes ressources d’apprentissage se trouvent derrière ce lien.)


Update (2015): En ce qui concerne Java 8, j'utiliserais encore l'approche Guava, car elle est bien plus propre qu'autre chose. Si vous ne voulez pas de dépendance à la goyave, considérez un plaine ancienne méthode init . Le hack avec tableau à deux dimensions et Stream API est assez moche si vous me le demandez, et devient plus moche si vous devez créer une carte dont les clés et les valeurs ne sont pas du même type (comme Map<Integer, String> dans la question).

En ce qui concerne l'avenir de Guava en général, en ce qui concerne Java 8, Louis Wasserman a déclaré ceci en 2014, et [mise à jour] en 2016, il a été annoncé que Guava 21 nécessiterait et supporte correctement Java 8 .


Update (2016): Comme Tagir Valeev fait remarquer , Java 9 finira par rendre cette tâche propre en utilisant uniquement du JDK pur, en ajoutant méthodes de fabrique pratique pour les collections:

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

J'utiliserais:

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

    private static Map<Integer, String> createMap() {
        Map<Integer, String> result = new HashMap<Integer, String>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  1. il évite les cours anonymes, que je considère personnellement comme un mauvais style, et évite
  2. cela rend la création de carte plus explicite
  3. cela rend la carte non modifiable
  4. comme MY_MAP est constant, je le nommerais comme constant
169
Peter Štibraný

Java 5 fournit cette syntaxe plus compacte:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};
164
Chris Noe

Un des avantages de la deuxième méthode est que vous pouvez l’emballer avec Collections.unmodifiableMap() pour garantir que rien ne va mettre à jour la collection ultérieurement:

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!
88
Outlaw Programmer

Voici un initialiseur de carte statique une ligne 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: pour initialiser un Map<Integer, String> comme dans la question, vous avez besoin de quelque chose comme ceci:

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): i_am_zero propose une meilleure version compatible avec différents types qui utilise un flux d'appels new SimpleEntry<>(k, v). Découvrez cette réponse: https://stackoverflow.com/a/37384773/3950982

59
Luke Hutchison

En Java 9:

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

Voir JEP 269 pour plus de détails. Le JDK 9 a atteint disponibilité générale en septembre 2017. 

47
Tagir Valeev

Avec Collections Eclipse , tout ce qui suit fonctionnera:

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

Vous pouvez également initialiser de manière statique des cartes primitives avec 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");
} 

Note: Je suis un partisan des collections Eclipse 

30
Donald Raab

Java 9

Nous pouvons utiliser Map.ofEntries comme:

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

Nous pouvons également utiliser Map.of comme suggéré par Tagir dans sa réponse ici mais nous ne pouvons pas avoir plus de 10 entrées utilisant Map.of.

Java 8 (solution soignée)

Nous pouvons créer un flux d'entrées de carte. Nous avons déjà deux implémentations de Entry dans Java.util.AbstractMap qui sont SimpleEntry et SimpleImmutableEntry . Pour cet exemple, nous pouvons utiliser ancien comme:

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

Je ne créerais jamais de sous-classe anonyme dans cette situation. Les initialiseurs statiques fonctionnent également bien si vous souhaitez rendre la carte non modifiable, par exemple:

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

Peut-être est-il intéressant de consulter Google Collections , par exemple. les vidéos qu'ils ont sur leur page. Ils fournissent divers moyens d'initialiser des cartes et des ensembles, ainsi que des collections immuables.

Mise à jour: Cette bibliothèque s'appelle maintenant Guava .

16
Kaarel

J'aime les cours anonymes, car il est facile d'y faire face:

public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
    {
        put(1, "some value");
                    //rest of code here
    }
});
16
Shushant
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);
    }
}

Si nous déclarons plus d'une constante, alors ce code sera écrit en bloc statique et il sera difficile à maintenir à l'avenir. Il est donc préférable d'utiliser une classe anonyme.

public class Test {

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

Et il est suggéré d'utiliser unmodifiableMap pour les constantes, sinon il ne peut pas être traité comme constant.

11

Je pourrais fortement suggérer le style "initialisation à double accolade" par rapport au style de bloc statique.

Quelqu'un peut dire qu'ils n'aiment pas les cours anonymes, les frais généraux, les performances, etc.

Mais ce que je considère plus est la lisibilité du code et la maintenabilité. De ce point de vue, je considère qu'une double attache constitue un meilleur style de code plutôt qu'une méthode statique.

  1. Les éléments sont imbriqués et en ligne.
  2. C'est plus OO, pas procédural.
  3. l'impact sur les performances est vraiment faible et pourrait être ignoré.
  4. Meilleure prise en charge du contour IDE (plutôt que de nombreux blocs statiques {} anonymes)
  5. Vous avez enregistré quelques lignes de commentaire pour leur apporter une relation.
  6. Empêchez la possibilité de fuite d'élément/d'instance d'objet non initialisé provenant de l'optimiseur d'exceptions et de bytecode.
  7. Ne vous inquiétez pas de l'ordre d'exécution du bloc statique.

De plus, si vous connaissez le GC de la classe anonyme, vous pouvez toujours le convertir en HashMap normal en utilisant new HashMap(Map map).

Vous pouvez le faire jusqu'à ce que vous rencontriez un autre problème. Si vous le faites, vous devez utiliser un autre style de codage complet (par exemple, aucune classe d'usine ou statique).

10
Dennis C

Comme d'habitude, Apache-commons a la bonne méthode MapUtils.putAll (Map, Object []) :

Par exemple, pour créer une carte de couleurs: 

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

Voici mon préféré lorsque je ne veux pas (ou ne peux pas) utiliser le ImmutableMap.of() de Guava, ou si j'ai besoin d'une variable 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]);
        }
    }};
}

Il est très compact et ignore les valeurs parasites (c’est-à-dire une clé finale sans valeur).

Usage:

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

Je préfère utiliser un initialiseur statique pour éviter de générer des classes anonymes (qui n'auraient aucun autre but), je vais donc répertorier les astuces qui initialisent avec un initialiseur statique. Toutes les solutions/astuces listées sont sécurisées.

Remarque: La question ne dit rien sur le fait de rendre la carte non modifiable, donc je vais laisser cela de côté, mais sachez que cela peut facilement être fait avec Collections.unmodifiableMap(map).

Premier conseil

Le premier conseil est que vous pouvez faire une référence locale à la carte et lui donner un nom court:

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

Deuxième conseil

Le deuxième conseil est que vous pouvez créer une méthode d'assistance pour ajouter des entrées; vous pouvez également rendre cette méthode d'assistance publique si vous voulez:

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

La méthode d'assistance ici n'est pas réutilisable car elle ne peut ajouter que des éléments à myMap2. Pour la rendre réutilisable, nous pourrions faire de la carte elle-même un paramètre de la méthode d'assistance, mais le code d'initialisation ne serait pas plus court. 

Troisième astuce

Le troisième conseil est que vous pouvez créer une classe d'assistance réutilisable semblable à un générateur avec la fonctionnalité de remplissage. Il s’agit vraiment d’une classe d’aide simple, de 10 lignes, qui respecte le type:

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
    }
}
5
icza

La classe anonyme que vous créez fonctionne bien. Cependant, vous devez savoir qu'il s'agit d'une classe inner et qu'elle contient donc une référence à l'instance de classe environnante. Ainsi, vous constaterez que vous ne pouvez pas faire certaines choses avec (en utilisant XStream for one). Vous obtiendrez des erreurs très étranges.

Cela dit, tant que vous en êtes conscient, cette approche est acceptable. Je l'utilise la plupart du temps pour initialiser de manière concise toutes sortes de collections. 

EDIT: A bien souligné dans les commentaires qu'il s'agit d'une classe statique. Évidemment, je n'ai pas lu cela assez attentivement. Cependant, mes commentaires do s'appliquent toujours aux classes internes anonymes.

5
Brian Agnew

Si vous voulez quelque chose de léger et de relativement sûr, vous pouvez simplement passer la vérification du type à la compilation au moment de l'exécution:

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

Cette implémentation devrait intercepter toutes les erreurs:

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

Vous pouvez utiliser StickyMap et MapEntry from Cactoos :

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

Avec Java 8, je viens d'utiliser le modèle suivant:

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

Ce n'est pas le plus sommaire et un peu rond-point, mais

  • il ne nécessite rien en dehors de Java.util
  • il est dactylographié et accueille facilement différents types de clés et de valeurs 
4
zrvan

Votre seconde approche (Initialisation Double Brace) est considérée comme un anti pattern , donc je choisirais la première approche.

Un autre moyen simple d’initialiser une carte statique consiste à utiliser cette fonction utilitaire:

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

Remarque: dans Java 9, vous pouvez utiliser Map.of

3
R. Oosterholt

Comme Java ne prend pas en charge les littéraux de carte, les instances de carte doivent toujours être explicitement instanciées et renseignées.

Heureusement, il est possible d'approcher le comportement des littéraux de carte en Java à l'aide deméthodes d'usine

Par exemple:

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

Sortie:

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

C’est beaucoup plus pratique que de créer et d’alimenter la carte, élément par élément.

3
nazar_art

Je n'ai pas vu l'approche que j'utilise (et que j'ai appris à aimer) dans aucune réponse, alors la voici:

Je n'aime pas utiliser les initialiseurs statiques car ils sont maladroits, .__ et je n'aime pas les classes anonymes, car cela crée une nouvelle classe pour chaque instance.

au lieu de cela, je préfère une initialisation qui ressemble à ceci:

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

malheureusement, ces méthodes ne font pas partie de la bibliothèque Java standard, Vous devrez donc créer (ou utiliser) une bibliothèque d’utilitaires qui définit les méthodes suivantes:

 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)

(vous pouvez utiliser 'import static' pour éviter d'avoir à préfixer le nom de la méthode)

J'ai trouvé utile de fournir des méthodes statiques similaires pour les autres collections (liste, ensemble, triSet, sortMap, etc.)

Son initialisation n’est pas aussi agréable que celle de json, mais c’est un pas dans cette direction en ce qui concerne la lisibilité.

3
josh

Je n'aime pas la syntaxe d'initialiseur statique et je ne suis pas convaincu par les sous-classes anonymes. En général, je suis d’accord avec tous les inconvénients de l’utilisation des initialiseurs statiques et de l’utilisation des sous-classes anonymes mentionnées dans les réponses précédentes. D'autre part, les avantages présentés dans ces publications ne me suffisent pas. Je préfère utiliser la méthode d'initialisation statique:

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;
    }
}
3
Stanisław Borowy

Si vous avez seulement besoin d’ajouter une valeur à la carte, vous pouvez utiliser Collections.singletonMap

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

J'ai lu les réponses et j'ai décidé d'écrire mon propre constructeur de carte. N'hésitez pas à copier-coller et profiter.

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: Dernièrement, je continue à trouver la méthode statique publique of assez souvent et je l’aime un peu. Je l'ai ajouté dans le code et rendu le constructeur privé, basculant ainsi vers un modèle de méthode d'usine statique.

EDIT2: Encore plus récemment, je n’aimais plus la méthode statique appelée of, car elle semble très mauvaise lors de l’utilisation d’importations statiques. Je l'ai renommé en mapOf, ce qui le rend plus adapté aux importations statiques.

2
Vlasec

JEP 269 fournit des méthodes de fabrique pratiques pour l’API de collections. Ces méthodes d'usine ne sont pas dans la version Java actuelle, à savoir 8, mais sont planifiées pour Java 9. 

Pour Map, il existe deux méthodes d'usine: of et ofEntries. En utilisant of, vous pouvez transmettre des paires clé/valeur en alternance. Par exemple, pour créer une Map comme {age: 27, major: cs}:

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

Il existe actuellement dix versions surchargées pour of. Vous pouvez donc créer une carte contenant dix paires clé/valeur. Si vous n'aimez pas cette limitation ou l'alternance clé/valeur, vous pouvez utiliser ofEntries:

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

of et ofEntries renverront un Map immuable, vous ne pourrez donc pas modifier leurs éléments après la construction. Vous pouvez essayer ces fonctionnalités avec JDK 9 Early Access .

2
Ali Dehghani

Eh bien ... j'aime les énums;)

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

Celui-ci utilise Apache commons-lang qui sera probablement déjà sur votre chemin de classe:

Map<String, String> collect = Stream.of(
        Pair.of("hello", "world"),
        Pair.of("abc", "123"),
        Pair.of("Java", "eight")
).collect(Collectors.toMap(Pair::getKey, Pair::getValue));
1
egridasov

En Java 8, l’approche procédurale peut également être encapsulée dans Supplier:

Map<String,String> m = ((Supplier<Map<String,String>>)(() -> {
    Map<String,String> result = new HashMap<>();
    result.put("foo","hoo");
    ...
    return result;
)).get();

Ce n'est qu'une façon hypothétique, mais peut s'avérer utile si vous avez vraiment besoin d'un one-liner.

0
Tomáš Záluský

Même avec la classe Nice ImmutableMap de Guava, j'aimerais parfois créer une carte modifiable couramment. En voulant éviter les blocs statiques et le sous-type anonyme, lorsque Java 8 est apparu, j'ai écrit une petite bibliothèque appelée Fluent .

// simple usage, assuming someMap is a Map<String, String> already declared
Map<String, String> example = new Fluent.HashMap<String, String>()
    .append("key1", "val1")
    .append("key2", "val2")
    .appendAll(someMap);

Avec l'interface Java 8 par défaut, je pouvais implémenter les méthodes Fluent.Map pour toutes les implémentations Java Map standard (par exemple, HashMap, ConcurrentSkipListMap, ... etc.) sans répétition fastidieuse.

Les cartes non modifiables sont également simples.

Map<String, Integer> immutable = new Fluent.LinkedHashMap<String, Integer>()
    .append("one", 1)
    .append("two", 2)
    .append("three", 3)
    .unmodifiable();

Voir https://github.com/alexheretic/fluent pour la source, la documentation et des exemples.

0
Alex Butler

Maintenant que Java 8 est sorti, cette question mérite d'être revue. Je me suis attelé à la tâche. On pourrait peut-être exploiter la syntaxe d'expression lambda pour obtenir une syntaxe littérale jolie et concise (mais sûre en ce qui concerne le type) qui ressemble à ceci:

        Map<String,Object> myMap = hashMap(
                bob -> 5,
                TheGimp -> 8,
                incredibleKoolAid -> "James Taylor",
                heyArnold -> new Date()
        );

        Map<String,Integer> typesafeMap = treeMap(
                a -> 5,
                bee -> 8,
                sea -> 13
                deep -> 21
        );

Exemple de code non testé sur https://Gist.github.com/galdosd/10823529 Serait curieux de connaître l'opinion des autres à ce sujet (c'est un peu diabolique ...)

0
Domingo Ignacio

Si vous pouvez utiliser une représentation sous forme de chaîne de vos données, cette option est également disponible dans Java 8:

static Map<Integer, String> MAP = Stream.of(
        "1=one",
        "2=two"
).collect(Collectors.toMap(k -> Integer.parseInt(k.split("=")[0]), v -> v.split("=")[1]));
0
Christian Ullenboom

J'aime utiliser la "technique" d'initialisation statique lorsque j'ai une réalisation concrète d'une classe abstraite qui a défini un constructeur d'initialisation mais pas de constructeur par défaut mais je souhaite que ma sous-classe ait un constructeur par défaut.

Par exemple:

public abstract class Shape {

    public static final String COLOR_KEY = "color_key";
    public static final String OPAQUE_KEY = "opaque_key";

    private final String color;
    private final Boolean opaque;

    /**
     * Initializing constructor - note no default constructor.
     *
     * @param properties a collection of Shape properties
     */
    public Shape(Map<String, Object> properties) {
        color = ((String) properties.getOrDefault(COLOR_KEY, "black"));
        opaque = (Boolean) properties.getOrDefault(OPAQUE_KEY, false);
    }

    /**
     * Color property accessor method.
     *
     * @return the color of this Shape
     */
    public String getColor() {
        return color;
    }

    /**
     * Opaque property accessor method.
     *
     * @return true if this Shape is opaque, false otherwise
     */
    public Boolean isOpaque() {
        return opaque;
    }
}

et ma réalisation concrète de cette classe - mais elle veut/a besoin d’un constructeur par défaut:

public class SquareShapeImpl extends Shape {

    private static final Map<String, Object> DEFAULT_PROPS = new HashMap<>();

    static {
        DEFAULT_PROPS.put(Shape.COLOR_KEY, "yellow");
        DEFAULT_PROPS.put(Shape.OPAQUE_KEY, false);
    }

    /**
     * Default constructor -- intializes this square to be a translucent yellow
     */
    public SquareShapeImpl() {
        // the static initializer was useful here because the call to 
        // this(...) must be the first statement in this constructor
        // i.e., we can't be mucking around and creating a map here
        this(DEFAULT_PROPS);
    }

    /**
     * Initializing constructor -- create a Square with the given
     * collection of properties.
     *
     * @param props a collection of properties for this SquareShapeImpl
     */
    public SquareShapeImpl(Map<String, Object> props) {
        super(props);
    }
}

puis pour utiliser ce constructeur par défaut, nous faisons simplement:

public class StaticInitDemo {

    public static void main(String[] args) {

        // create a translucent, yellow square...
        Shape defaultSquare = new SquareShapeImpl();

        // etc...
    }
}
0
Jim Daehn

J'aime la syntaxe de classe anonyme; c'est juste moins de code. Cependant, un inconvénient majeur que j'ai constaté est que vous ne pourrez pas sérialiser cet objet via la communication à distance. Vous obtiendrez une exception concernant l'impossibilité de trouver la classe anonyme du côté distant.

0
Chase Seibert

Voici le code par AbacusUtil

Map<Integer, String> map = N.asMap(1, "one", 2, "two");
// Or for Immutable map 
ImmutableMap<Integer, String> = ImmutableMap.of(1, "one", 2, "two");

Déclaration : Je suis le développeur de AbacusUtil.

0
user_3380739

Remarque: cette réponse appartient en fait à la question Comment initialiser directement un HashMap (de manière littérale)? mais comme il est marqué comme [duplicata] de celui-ci ...


Avant Java 9 avec son Map.of () (qui est également limité à 10 mappages), vous pouvez étendre une implémentation Map de votre choix, par exemple:

public class InitHashMap<K, V> extends HashMap<K, V>

ré-implémentez les constructeurs de HashMap:

public InitHashMap() {
    super();
}

public InitHashMap( int initialCapacity, float loadFactor ) {
    super( initialCapacity, loadFactor );
}

public InitHashMap( int initialCapacity ) {
    super( initialCapacity );
}

public InitHashMap( Map<? extends K, ? extends V> m ) {
    super( m );
}

et ajoutez un constructeur supplémentaire inspiré par la réponse d'Aerthel mais est générique en utilisant les types Object... et <K, V>:

public InitHashMap( final Object... keyValuePairs ) {

    if ( keyValuePairs.length % 2 != 0 )
        throw new IllegalArgumentException( "Uneven number of arguments." );

    K key = null;
    int i = -1;

    for ( final Object keyOrValue : keyValuePairs )
        switch ( ++i % 2 ) {
            case 0:  // key
                if ( keyOrValue == null )
                    throw new IllegalArgumentException( "Key[" + (i >> 1) + "] is <null>." );
                key = (K) keyOrValue;
                continue;
            case 1:  // value
                put( key, (V) keyOrValue );
        }
}

Courir

public static void main( final String[] args ) {

    final Map<Integer, String> map = new InitHashMap<>( 1, "First", 2, "Second", 3, "Third" );
    System.out.println( map );
}

Sortie

{1=First, 2=Second, 3=Third}

Vous pouvez également étendre l'interface Map de la même manière:

public interface InitMap<K, V> extends Map<K, V> {

    static <K, V> Map<K, V> of( final Object... keyValuePairs ) {

        if ( keyValuePairs.length % 2 != 0 )
            throw new IllegalArgumentException( "Uneven number of arguments." );

        final Map<K, V> map = new HashMap<>( keyValuePairs.length >> 1, .75f );
        K key = null;
        int i = -1;

        for ( final Object keyOrValue : keyValuePairs )
            switch ( ++i % 2 ) {
                case 0: // key
                    if ( keyOrValue == null )
                        throw new IllegalArgumentException( "Key[" + (i >> 1) + "] is <null>." );
                    key = (K) keyOrValue;
                    continue;
                case 1: // value
                    map.put( key, (V) keyOrValue );
            }
        return map;
    }
}

Courir

public static void main( final String[] args ) {

    System.out.println( InitMap.of( 1, "First", 2, "Second", 3, "Third" ) );
}

Sortie

{1=First, 2=Second, 3=Third}
0
Gerold Broser

J'ai fait quelque chose d'un peu différent. Pas le meilleur, mais ça marche pour moi. Peut-être que cela pourrait être "générique".

private static final Object[][] ENTRIES =
{
  {new Integer(1), "one"},
  {new Integer(2), "two"},
};
private static final Map myMap = newMap(ENTRIES);

private static Map newMap(Object[][] entries)
{
  Map map = new HashMap();

  for (int x = 0; x < entries.length; x++)
  {
    Object[] entry = entries[x];

    map.put(entry[0], entry[1]);
  }

  return map;
}
0
Gary Kephart

Il y a quelques bonnes réponses ici, mais je veux en proposer une de plus.

Créez votre propre méthode statique pour créer et initialiser une Map. J'ai ma propre classe CollectionUtils dans un package que j'utilise dans des projets avec divers utilitaires que j'utilise régulièrement, que je peux facilement écrire et évite le besoin d'une dépendance à une bibliothèque plus grande.

Voici ma méthode newMap:

public class CollectionUtils {
    public static Map newMap(Object... keyValuePairs) {
        Map map = new HashMap();
        if ( keyValuePairs.length % 2 == 1 ) throw new IllegalArgumentException("Must have even number of arguments");
        for ( int i=0; i<keyValuePairs.length; i+=2 ) {
            map.put(keyValuePairs[i], keyValuePairs[i + 1]);
        }
        return map;
    }
}

Usage:

import static CollectionUtils.newMap;
// ...
Map aMap = newMap("key1", 1.23, "key2", 2.34);
Map bMap = newMap(objKey1, objVal1, objKey2, objVal2, objKey3, objVal3);
// etc...

Il ne fait pas usage de génériques, mais vous pouvez dactylographier la carte à votre guise (assurez-vous simplement de la transtyper correctement!)

Map<String,Double> aMap = (Map<String,Double>)newMap("key1", 1.23, "key2", 2.34);
0
Jason

La seconde méthode peut appeler des méthodes protégées si nécessaire. Cela peut être utile pour initialiser des classes qui sont immuables après la construction.

0
Mark Renouf