Comment initialiser une carte statique?


1132

Comment initialiseriez-vous une statique Mapen Java?

Première méthode: initialiseur statique Deuxième
méthode: 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<>();
    static {
        myMap.put(1, "one");
        myMap.put(2, "two");
    }

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

2
Pour initialiser une carte en Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal

2
S'il vous plaît, n'utilisez jamais l' initialisation à double accolade - c'est un hack et un moyen facile de fuir la mémoire et de causer d'autres problèmes.
dimo414

Java 9? Si les entrées comptent <= 10, utilisez Map.ofautre chose Map.ofEntries, consultez stackoverflow.com/a/37384773/1216775
akhil_mittal

Réponses:


1106

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

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

10
C'est l'idiome que j'utilise depuis des années et je n'ai jamais vu personne y penser. Je fais de même pour les ensembles et listes constants non modifiables.
jasonmp85

3
Comment gérer un HashMap <String, String> avec une clé String. L'objet Map ne me permet pas d'avoir une clé String donc je ne peux pas utiliser unmodifiableMap (). Je suppose que lancer un HashMap irait également à l'encontre du but. Des idées?
Luke

30
@Luke Je doute fortement qu'Android ait une telle limitation. Cela n'a aucun sens. Une recherche rapide a trouvé cette question ici (et bien d'autres) qui semble impliquer que vous pouvez utiliser une clé de chaîne pour un objet Map dans Android.
mluisbrown

11
Donc, personne d'autre ne se soucie d'enquêter, je peux confirmer qu'il n'y a pas de problème avec l'utilisation d'une clé String pour un objet Map sur Android.
Jordan

11
Jordan: c'est un vieux sujet maintenant mais je soupçonne que @Luke essayait d'utiliser une chaîne comme clé dans une carte qui avait un type de clé différent, par exemple Map <Integer, String>.
Variable misérable du

445

J'aime la manière Guava d'initialiser une carte statique et immuable:

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

Comme vous pouvez le voir, c'est très concis (en raison des méthodes d'usine pratiques ImmutableMap).

Si vous souhaitez que la carte comporte plus de 5 entrées, vous ne pouvez plus l'utiliser ImmutableMap.of(). Essayez plutôt de ImmutableMap.builder()suivre 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 collecte immuables de Guava, voir Collections immuables expliquées dans le Guide de l'utilisateur de Guava .

(Un sous-ensemble de) La goyave s'appelait Google Collections . Si vous n'utilisez pas encore cette bibliothèque dans votre projet Java, je vous recommande fortement de l' essayer! La goyave est rapidement devenue l'une des bibliothèques tierces gratuites les plus populaires et les plus utiles pour Java, comme en conviennent les autres utilisateurs SO . (Si vous êtes novice, il existe d'excellentes ressources d'apprentissage derrière ce lien.)


Mise à jour (2015) : Quant à Java 8 , eh bien, j'utiliserais toujours l'approche Guava car elle est bien plus propre qu'autre chose. Si vous ne voulez pas de dépendance Guava, envisagez une ancienne méthode init simple . Le hack avec tableau bidimensionnel et API Stream est assez moche si vous me demandez, et devient plus moche si vous avez besoin de 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).

Quant à l'avenir de Guava en général, en ce qui concerne Java 8, Louis Wasserman l'a dit en 2014, et [ mise à jour ] en 2016, il a été annoncé que Guava 21 nécessitera et supportera correctement Java 8 .


Mise à jour (2016) : Comme le souligne Tagir Valeev , Java 9 rendra finalement cette opération propre en n'utilisant que du JDK pur, en ajoutant des méthodes d'usine pratiques pour les collections:

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

21
On dirait que nos confrères SO ont supprimé la vénérable question "Les bibliothèques Java tierces gratuites les plus utiles" à laquelle j'ai lié. :( Merde.
Jonik

2
Je suis d'accord, c'est la meilleure façon d'initialiser une carte constante. Non seulement plus lisible mais aussi puisque Collections.unmodifiableMap renvoie une vue en lecture seule de la carte sous-jacente (qui peut toujours être modifiée).
crunchdog

11
Je peux maintenant voir les questions supprimées (avec 10k + rep), voici donc une copie de «Les bibliothèques Java tierces gratuites les plus utiles» . Ce n'est que la première page, mais au moins vous pouvez trouver les ressources de la goyave mentionnées ci-dessus.
Jonik

2
Je préfère vraiment cette approche, bien qu'il soit avantageux de savoir comment le faire sans dépendances supplémentaires.
Clé

2
JEP 186 n'est toujours pas fermé, il peut donc introduire de nouvelles fonctionnalités liées aux littéraux de collection
cybersoft

182

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<>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  1. cela évite une classe anonyme, que je considère personnellement comme un mauvais style, et évite
  2. il rend la création de carte plus explicite
  3. cela rend la carte non modifiable
  4. comme MY_MAP est constant, je l'appellerais constant

3
Parmi les options JDK pures (pas de bibliothèques), j'aime le plus, car la définition de la carte est clairement liée à son initialisation. Également convenu d'une dénomination constante.
Jonik

Il ne m'est jamais venu à l'esprit que vous pouviez faire cela.
romulusnr

181

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

46
Cette technique est appelée initialisation à double accolade: stackoverflow.com/questions/1372113/… Ce n'est pas une syntaxe Java 5 spéciale, c'est juste une astuce avec une classe anonyme avec un initialiseur d'instance.
Jesper

13
Question rapide concernant l'initialisation à double accolade: lors de cette opération, Eclipse émet un avertissement concernant un ID de série manquant. D'une part, je ne vois pas pourquoi un ID de série serait nécessaire dans ce cas spécifique, mais d'autre part, je n'aime généralement pas supprimer les avertissements. Que pensez-vous de ceci?
nbarraille

8
@nbarraille C'est parce que HashMap implements Serializable. Puisque vous créez réellement une sous-classe de HashMap en utilisant cette "astuce", vous créez implicitement une classe Serializable. Et pour cela, vous devez fournir un serialUID.
personne

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 - L'utilisation suggérée est un contexte statique. Les performances peuvent être pires, mais pas de façon notable lorsqu'il s'agit d'un petit nombre vraisemblablement de cartes définies statiquement. HashMap.equalsest défini dans AbstractMapet fonctionne sur n'importe quelle sous-classe de Map, ce n'est donc pas un problème ici. L'opérateur de diamant est ennuyeux, mais comme mentionné, il a maintenant été résolu.
Jules

95

Un avantage de la deuxième méthode est que vous pouvez la boucler Collections.unmodifiableMap()pour garantir que rien ne mettra à jour la collection plus tard:

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!

3
Ne pouvez-vous pas le faire facilement dans la première méthode en déplaçant le nouvel opérateur dans le bloc statique {} et en l'enveloppant?
Patrick

2
Je déplacerais quand même l'appel constructeur dans l'initialisation statique. Tout le reste semble étrange.
Tom Hawtin - tackline

2
une idée de la performance qui pourrait résulter de l'utilisation d'une classe anonyme par opposition à une classe concrète?
Kip

62

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

Modifier: pour initialiser un Map<Integer, String>comme dans la question, vous auriez 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): il y a une meilleure version de type mixte par i_am_zero qui utilise un flux d' new SimpleEntry<>(k, v)appels. Découvrez cette réponse: https://stackoverflow.com/a/37384773/3950982


7
J'ai pris la liberté d'ajouter une version équivalente à la question et aux autres réponses: init une carte dont les clés et les valeurs sont de type différent (donc String[][]ne fera pas, Object[][]est nécessaire). À mon humble avis, cette approche est laide (encore plus avec les moulages) et difficile à retenir; ne l'utiliserais pas moi-même.
Jonik

57

Map.of 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. JDK 9 a atteint la disponibilité générale en septembre 2017.


7
Ou si vous voulez plus de 10 paires valeur-clé, vous pouvez utiliserMap.ofEntries
ZhekaKozlov

8
C'est propre et tout, jusqu'à ce que vous réalisiez comment il a été mis en œuvre
mi

Ugh thats so sad - on dirait qu'il ne prend en charge que 10 entrées, après quoi vous devez utiliser des entrées. Boiteux.
Somaiah Kumbera

2
La propreté de la mise en œuvre dans le JDK ne devrait pas avoir d'importance tant qu'il fonctionne et satisfait le contrat. Comme toute boîte noire, les détails de mise en œuvre peuvent toujours être corrigés à l'avenir si vraiment nécessaire ...
vikingsteve

@mid C'est le seul moyen sûr de faire cela en Java.
Luke Hutchison

44

Java 9

Nous pouvons utiliser Map.ofEntries, appelant Map.entry( k , v )pour créer chaque entrée.

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.ofcomme suggéré par Tagir dans sa réponse ici, mais nous ne pouvons pas utiliser plus de 10 entrées 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 Entrydans java.util.AbstractMaplesquelles sont SimpleEntry et SimpleImmutableEntry . Pour cet exemple, nous pouvons utiliser l'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));

2
Le new SimpleEntry<>()chemin est beaucoup moins lisible que statique put(): /
Danon

32

Avec les collections Eclipse , tous les éléments suivants fonctionneront:

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 statiquement des cartes primitives avec les collections 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<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");
} 

Remarque: je suis un committer pour les collections Eclipse


1
Je souhaite vraiment qu'Eclipse Collections soit la bibliothèque de collections par défaut pour Java. Je l'apprécie tellement plus que Guava + JCL.
Kenny Cason

29

Je ne créerais jamais une 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);
}

1
Dans quelle situation utiliseriez-vous une sous-classe anonyme pour initialiser une table de hachage alors?
dogbane

6
Ne jamais initialiser une collection.
eljenso

Pourriez-vous expliquer pourquoi l'utilisation d'un initialiseur statique est un meilleur choix que la création d'une sous-classe anonyme?
leba-lev

3
@rookie Il y a plusieurs raisons données dans d'autres réponses favorisant l'init statique. Le but ici est d'initialiser, alors pourquoi introduire le sous-classement, sauf peut-être pour économiser quelques touches? (Si vous souhaitez économiser sur les frappes, Java n'est certainement pas un bon choix en tant que langage de programmation.) Une règle de base que j'utilise lors de la programmation en Java est: sous-classe aussi peu que possible (et jamais quand cela peut être raisonnablement évité).
eljenso

@eljenso - la raison pour laquelle je préfère généralement la syntaxe de la sous-classe est qu'elle met l'initialisation en ligne, où elle appartient . Un deuxième choix consiste à appeler une méthode statique qui renvoie la carte initialisée. Mais j'ai peur de regarder votre code et de passer quelques secondes à découvrir d'où vient MY_MAP, et c'est le moment que je ne veux pas perdre. Toute amélioration de la lisibilité est un bonus, et les conséquences sur les performances sont minimes, donc cela me semble être la meilleure option.
Jules

18

Il est peut-être intéressant de consulter Google Collections , par exemple les vidéos qu'ils ont sur leur page. Ils offrent différentes façons d'initialiser des cartes et des ensembles, ainsi que des collections immuables.

Mise à jour: cette bibliothèque s'appelle désormais Guava .


17

J'aime les cours anonymes, car c'est facile à gérer:

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

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

Si nous déclarons plus d'une constante, alors ce code sera écrit dans un bloc statique et c'est 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.


10

Je pourrais fortement suggérer le style "initialisation à double accolade" plutôt que le style de bloc statique.

Quelqu'un peut dire qu'il n'aime pas les classes anonymes, les frais généraux, les performances, etc.

Mais ce que je considère plus, c'est la lisibilité et la maintenabilité du code. De ce point de vue, je pense qu'un double croisillon est 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 des contours IDE (plutôt que de nombreux blocs {} statiques anonymes)
  5. Vous avez enregistré quelques lignes de commentaires pour leur apporter une relation.
  6. Empêcher la fuite d'éléments / plomb d'instance possible de l'objet non initialisé de l'optimiseur d'exception 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, pas de classe statique ou d'usine).


8

Comme d'habitude, apache-commons a la méthode appropriée 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"}
 });

J'inclus Apache Commons dans toutes les versions, donc, en l'absence regrettable d'une méthode Arrays.asMap( ... )en langage Java simple, je pense que c'est la meilleure solution. Réinventer la roue est généralement idiot. Très léger inconvénient, c'est qu'avec les génériques, il faudra une conversion non contrôlée.
Mike Rodent

@mikerodent version 4.1 est générique: (array carte finale <K, V> carte, objet final []) public static <K, V> Carte <K, V> putAll
agad

Tx ... oui, j'utilise 4.1 mais je dois encore le faire SuppressWarnings( unchecked )dans Eclipse avec une ligne commeMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
mike rodent

@mikerodent n'est-ce pas à cause de l' objet [] [] ? Voir mise à jour non-jure - Je n'ai aucun avertissement dans Eclipse.
7h49

Comme c'est étrange ... même quand je pars, String[][]j'obtiens "l'avertissement"! Et bien sûr, cela ne fonctionne que si votre Ket Vsont de la même classe. Je suppose que vous n'avez pas (naturellement) défini la "conversion non contrôlée" sur "Ignorer" dans votre configuration Eclipse?
Mike rongeur

7

Voici mon préféré quand je ne veux pas (ou ne peux pas) utiliser les goyaves ImmutableMap.of(), ou si j'ai besoin d'un mutable 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

Si vous voulez une carte non modifiable, java 9 a enfin ajouté une méthode d'usine sympa ofà l' Mapinterface. Une méthode similaire est également ajoutée à Set, List.

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


6

Je préfère utiliser un initialiseur statique pour éviter de générer des classes anonymes (qui n'auraient plus de raison d'être), donc je vais lister des astuces pour l'initialisation avec un initialiseur statique. Toutes les solutions / conseils répertoriés sont de type sécurisé.

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

La première astuce 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

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

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 cependant pas réutilisable car elle ne peut qu'ajouter des éléments à myMap2. Pour le rendre réutilisable, nous pourrions faire de la carte elle-même un paramètre de la méthode d'assistance, mais alors le code d'initialisation ne serait pas plus court.

Troisième conseil

La troisième astuce est que vous pouvez créer une classe d'aide de type constructeur réutilisable avec la fonctionnalité de remplissage. Il s'agit vraiment d'une classe auxiliaire simple de 10 lignes qui est de type sécurisé:

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

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

Cela dit, tant que vous le savez, cette approche est très bien. Je l'utilise la plupart du temps pour initialiser toutes sortes de collections de façon concise.

EDIT: a souligné correctement dans les commentaires qu'il s'agit d'une classe statique. Évidemment, je ne l'ai pas lu assez attentivement. Cependant mes commentaires ne s'appliquent toujours aux classes internes anonymes.


3
Dans ce cas particulier, il est statique, donc pas d'instance externe.
Tom Hawtin - tackline

On peut dire que XStream ne devrait pas essayer de sérialiser des choses comme ça (c'est statique. Pourquoi auriez-vous besoin de sérialiser une variable statique?)
jasonmp85

5

Si vous voulez quelque chose de concis et de relativement sûr, vous pouvez simplement déplacer la vérification du type de compilation à 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 détecter 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

Avec Java 8, j'en suis venu à 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 laconique et un peu détourné, mais

  • il ne nécessite rien en dehors de java.util
  • il est sûr et accepte facilement différents types de clés et de valeur.

si nécessaire, on peut utiliser la toMapsignature incluant un fournisseur de carte pour spécifier le type de carte.
zrvan


4

Vous pouvez utiliser StickyMapet MapEntrydepuis Cactoos :

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

4

On pense que votre deuxième approche (initialisation Double Brace) est un anti pattern , donc j'irais pour 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: Java 9vous pouvez utiliser Map.of


3

Je n'aime pas la syntaxe d'initialisation statique et je ne suis pas convaincu des sous-classes anonymes. En général, je suis d'accord avec tous les inconvénients de l'utilisation des initialiseurs statiques et tous les inconvénients de l'utilisation des sous-classes anonymes qui ont été mentionnées dans les réponses précédentes. D'un autre côté - les pros présentés dans ces articles 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

Je n'ai pas vu l'approche que j'utilise (et j'ai appris à aimer) publiée dans les réponses, alors 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.

à la place, 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 statique' 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 (list, set, sortedSet, sortedMap, etc.)

Ce n'est pas aussi agréable que l'initialisation d'un objet json, mais c'est un pas dans cette direction, en ce qui concerne la lisibilité.


3

Étant donné que Java ne prend pas en charge les littéraux de carte, les instances de carte doivent toujours être explicitement instanciées et remplies.

Heureusement, il est possible d'approximer le comportement des littéraux de carte en Java en utilisant des mé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)));
    }
}

Production:

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

C'est beaucoup plus pratique que de créer et de remplir la carte un élément à la fois.


2

JEP 269 fournit des méthodes d'usine pratiques pour l'API Collections. Ces méthodes d'usine ne sont pas dans la version Java actuelle, qui est 8, mais sont prévues pour la version Java 9.

Car Mapil existe deux méthodes d'usine: ofet ofEntries. À l'aide de of, vous pouvez transmettre des paires clé / valeur alternées. Par exemple, pour créer un Mapsemblable {age: 27, major: cs}:

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

Actuellement, il existe 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é / valeurs, vous pouvez utiliser ofEntries:

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

Les deux ofet ofEntriesrenverront un immuable Map, vous ne pouvez donc pas changer leurs éléments après la construction. Vous pouvez essayer ces fonctionnalités en utilisant JDK 9 Early Access .


2

Eh bien ... j'aime les énumérations;)

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

J'ai lu les réponses et j'ai décidé d'écrire mon propre constructeur de cartes. N'hésitez pas à copier-coller et à en 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 des méthodes statiques publiques ofassez souvent et j'aime un peu ça. Je l'ai ajouté dans le code et rendu le constructeur privé, passant ainsi au modèle de méthode d'usine statique.

EDIT2: Encore plus récemment, je n'aime plus la méthode statique appelée of, car elle semble assez mauvaise lors de l'utilisation d'importations statiques. Je l'ai renommé à la mapOfplace, ce qui le rend plus adapté aux importations statiques.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.