constructeur pour HashMap

109

Guava nous fournit d'excellentes méthodes d'usine pour les types Java, tels que Maps.newHashMap().

Mais existe-t-il aussi des constructeurs pour Java Maps?

HashMap<String,Integer> m = Maps.BuildHashMap.
    put("a",1).
    put("b",2).
    build();
Elazar Leibovich
la source

Réponses:

20

Puisque l' Mapinterface Java 9 contient:

  • Map.of(k1,v1, k2,v2, ..)
  • Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ..).

Les limites de ces méthodes d'usine sont qu'elles:

  • ne peut pas tenir null s comme clés et / ou valeurs (si vous avez besoin de stocker des valeurs nulles, jetez un œil à d'autres réponses)
  • produire des cartes immuables

Si nous avons besoin d'une carte mutable (comme HashMap), nous pouvons utiliser son constructeur de copie et le laisser copier le contenu de la carte créée viaMap.of(..)

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
Pshemo
la source
2
Notez que les méthodes Java 9 n'autorisent pas les nullvaleurs, ce qui peut poser problème selon le cas d'utilisation.
Per Lundberg
@JoshM. IMO Map.of(k1,v1, k2,v2, ...)peut être utilisé en toute sécurité lorsque nous n'avons pas beaucoup de valeurs. Pour une plus grande quantité de valeurs Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)nous donne un code plus lisible qui est moins sujet aux erreurs (sauf si je vous ai mal compris).
Pshemo
Vous avez bien compris. Le premier est vraiment dégoûtant pour moi; Je refuse de l'utiliser!
Josh M.
164

Il n'y a rien de tel pour HashMaps, mais vous pouvez créer un ImmutableMap avec un générateur:

final Map<String, Integer> m = ImmutableMap.<String, Integer>builder().
      put("a", 1).
      put("b", 2).
      build();

Et si vous avez besoin d'une carte mutable, vous pouvez simplement la transmettre au constructeur HashMap.

final Map<String, Integer> m = Maps.newHashMap(
    ImmutableMap.<String, Integer>builder().
        put("a", 1).
        put("b", 2).
        build());
Sean Patrick Floyd
la source
43
ImmutableMapne prend pas en charge les nullvaleurs. Il y a donc une limitation de cette approche: vous ne pouvez pas définir de valeurs dans votre HashMapto null.
vitaly
5
sean-patrick-floyd Eh bien, un exemple pratique: le NamedParameterJdbcTemplate de Spring attend une carte de valeurs indexées par des noms de paramètres. Disons que je veux utiliser NamedParameterJdbcTemplate pour définir une valeur de colonne sur null. Je ne vois pas: a) comment c'est une odeur de code; b) comment utiliser le modèle d'objet nul ici
vitaly
2
@vitaly ne peut pas discuter avec ça
Sean Patrick Floyd
2
Y a-t-il quelque chose de mal à utiliser le new HashMapconstructeur Java au lieu de la Maps.newHashMapméthode statique ?
CorayJeu
1
@CorayThan - Jonik a raison, c'est juste un raccourci reposant sur l'inférence de type statique. stackoverflow.com/a/13153812
AndersDJohnson
46

Pas tout à fait un constructeur, mais utilisant un initialiseur:

Map<String, String> map = new HashMap<String, String>() {{
    put("a", "1");
    put("b", "2");
}};
Johan Sjöberg
la source
Attendre. Cela ne ferait-il pas map instanceof HashMapfaux? Cela semble être une idée pas terrible.
Elazar Leibovich
3
@Elazar map.getClass()==HashMap.classrenverra false. Mais c'est un test stupide de toute façon. HashMap.class.isInstance(map)devrait être préféré, et cela retournera vrai.
Sean Patrick Floyd
59
Cela dit: je pense toujours que cette solution est mauvaise.
Sean Patrick Floyd
11
Il s'agit d'un initialiseur d'instance, pas d'un initialiseur statique. Il est exécuté après le constructeur du super, mais avant le corps du constructeur, pour chaque constructeur de la classe. Le cycle de vie n'est pas très connu et j'évite donc cet idiome.
Joe Coder
14
C'est une très mauvaise solution et à éviter: stackoverflow.com/a/27521360/3253277
Alexandre DuBreuil
36

C'est similaire à la réponse acceptée, mais un peu plus propre, à mon avis:

ImmutableMap.of("key1", val1, "key2", val2, "key3", val3);

Il existe plusieurs variantes de la méthode ci-dessus, et elles sont parfaites pour créer des cartes statiques, immuables et immuables.

Jake Toronto
la source
4
J'ai demandé un constructeur. Vous êtes limité à une poignée d'éléments.
Elazar Leibovich
Bien et propre, mais cela me fait envie de l'opérateur => de Perl ... ce qui est une sensation étrange.
Aaron Maenpaa
10

En voici un très simple ...

public class FluentHashMap<K, V> extends java.util.HashMap<K, V> {
  public FluentHashMap<K, V> with(K key, V value) {
    put(key, value);
    return this;
  }

  public static <K, V> FluentHashMap<K, V> map(K key, V value) {
    return new FluentHashMap<K, V>().with(key, value);
  }
}

puis

import static FluentHashMap.map;

HashMap<String, Integer> m = map("a", 1).with("b", 2);

Voir https://gist.github.com/culmat/a3bcc646fa4401641ac6eb01f3719065

culmat
la source
J'aime la simplicité de votre approche. Surtout que nous sommes en 2017 (presque 2018 maintenant!), Et qu'il n'y a toujours pas une telle API dans le JDK
Milad Naseri
Ça a l'air vraiment cool, merci. @MiladNaseri, c'est fou que JDK n'ait toujours pas quelque chose comme ça dans son API, quelle honte.
improbable le
9

Un simple constructeur de carte est simple à écrire:

public class Maps {

    public static <Q,W> MapWrapper<Q,W> map(Q q, W w) {
        return new MapWrapper<Q, W>(q, w);
    }

    public static final class MapWrapper<Q,W> {
        private final HashMap<Q,W> map;
        public MapWrapper(Q q, W w) {
            map = new HashMap<Q, W>();
            map.put(q, w);
        }
        public MapWrapper<Q,W> map(Q q, W w) {
            map.put(q, w);
            return this;
        }
        public Map<Q,W> getMap() {
            return map;
        }
    }

    public static void main(String[] args) {
        Map<String, Integer> map = Maps.map("one", 1).map("two", 2).map("three", 3).getMap();
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }
    }
}
Agnès
la source
6

Vous pouvez utiliser:

HashMap<String,Integer> m = Maps.newHashMap(
    ImmutableMap.of("a",1,"b",2)
);

Ce n'est pas aussi élégant et lisible, mais fait le travail.

Elazar Leibovich
la source
1
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2);, meilleur?
lschin
Identique au constructeur, mais avec une quantité limitée de données, car il est implémenté avec des surcharges. Si vous n'avez que quelques éléments, je suppose que c'est préférable.
Elazar Leibovich
4

HashMapest mutable; il n'y a pas besoin de constructeur.

Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
ColinD
la source
Que faire si vous souhaitez initialiser un champ avec lui? Toute logique dans la même ligne est meilleure que la logique dispersée entre champ et c'tor.
Elazar Leibovich
@Elazar: Si vous souhaitez initialiser un champ avec des valeurs spécifiques connues au moment de la compilation comme celle-ci, vous voulez généralement que ce champ soit immuable et doit utiliser ImmutableSet. Si vous voulez vraiment qu'il soit modifiable, vous pouvez l'initialiser dans le constructeur ou un bloc d'initialisation d'instance ou un bloc d'initialisation statique s'il s'agit d'un champ statique.
ColinD le
1
Euh, aurait dû le dire ImmutableMapévidemment.
ColinD le
Je ne pense pas. Je préfère voir l'initialisation dans la même ligne de définition, puis les placer dans une initialisation non statique {{init();}}(pas dans le constructeur, car d'autres constructeurs pourraient l'oublier). Et c'est bien que ce soit une sorte d'action atomique. Si la carte est volatile, l'initialiser avec un constructeur garantit qu'elle est toujours soit nullou dans l'état final, jamais à moitié remplie.
Elazar Leibovich
1

Vous pouvez utiliser l'API fluent dans les collections Eclipse :

Map<String, Integer> map = Maps.mutable.<String, Integer>empty()
        .withKeyValue("a", 1)
        .withKeyValue("b", 2);

Assert.assertEquals(Maps.mutable.with("a", 1, "b", 2), map);

Voici un blog avec plus de détails et d'exemples.

Remarque: je suis un committer pour les collections Eclipse.

Donald Raab
la source
0

J'avais une exigence similaire il y a quelque temps. Cela n'a rien à voir avec la goyave mais vous pouvez faire quelque chose comme ça pour être en mesure de construire proprement unMap utilisant un constructeur couramment.

Créez une classe de base qui étend Map.

public class FluentHashMap<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 4857340227048063855L;

    public FluentHashMap() {}

    public FluentHashMap<K, V> delete(Object key) {
        this.remove(key);
        return this;
    }
}

Ensuite, créez le constructeur fluide avec des méthodes adaptées à vos besoins:

public class ValueMap extends FluentHashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public ValueMap() {}

    public ValueMap withValue(String key, String val) {
        super.put(key, val);
        return this;
    }

... Add withXYZ to suit...

}

Vous pouvez ensuite l'implémenter comme ceci:

ValueMap map = new ValueMap()
      .withValue("key 1", "value 1")
      .withValue("key 2", "value 2")
      .withValue("key 3", "value 3")
Tarka
la source
0

C'est quelque chose que j'ai toujours voulu, en particulier lors de la configuration des montages de test. Enfin, j'ai décidé d'écrire un constructeur simple et fluide qui pourrait créer n'importe quelle implémentation de Map - https://gist.github.com/samshu/b471f5a2925fa9d9b718795d8bbdfe42#file-mapbuilder-java

    /**
     * @param mapClass Any {@link Map} implementation type. e.g., HashMap.class
     */
    public static <K, V> MapBuilder<K, V> builder(@SuppressWarnings("rawtypes") Class<? extends Map> mapClass)
            throws InstantiationException,
            IllegalAccessException {
        return new MapBuilder<K, V>(mapClass);
    }

    public MapBuilder<K, V> put(K key, V value) {
        map.put(key, value);
        return this;
    }

    public Map<K, V> build() {
        return map;
    }
aathif
la source
0

En voici un que j'ai écrit

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

public class MapBuilder<K, V> {

    private final Map<K, V> map;

    /**
     * Create a HashMap builder
     */
    public MapBuilder() {
        map = new HashMap<>();
    }

    /**
     * Create a HashMap builder
     * @param initialCapacity
     */
    public MapBuilder(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * Create a Map builder
     * @param mapFactory
     */
    public MapBuilder(Supplier<Map<K, V>> mapFactory) {
        map = mapFactory.get();
    }

    public MapBuilder<K, V> put(K key, V value) {
        map.put(key, value);
        return this;
    }

    public Map<K, V> build() {
        return map;
    }

    /**
     * Returns an unmodifiable Map. Strictly speaking, the Map is not immutable because any code with a reference to
     * the builder could mutate it.
     *
     * @return
     */
    public Map<K, V> buildUnmodifiable() {
        return Collections.unmodifiableMap(map);
    }
}

Vous l'utilisez comme ceci:

Map<String, Object> map = new MapBuilder<String, Object>(LinkedHashMap::new)
    .put("event_type", newEvent.getType())
    .put("app_package_name", newEvent.getPackageName())
    .put("activity", newEvent.getActivity())
    .build();
Dónal
la source
0

En utilisant java 8:

C'est une approche de Java-9 Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)

public class MapUtil {
    import static java.util.stream.Collectors.toMap;

    import java.util.AbstractMap.SimpleEntry;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.stream.Stream;

    private MapUtil() {}

    @SafeVarargs
    public static Map<String, Object> ofEntries(SimpleEntry<String, Object>... values) {
        return Stream.of(values).collect(toMap(Entry::getKey, Entry::getValue));
    }

    public static SimpleEntry<String, Object> entry(String key, Object value) {
        return new SimpleEntry<String, Object>(key, value);
    }
}

Comment utiliser:

import static your.package.name.MapUtil.*;

import java.util.Map;

Map<String, Object> map = ofEntries(
        entry("id", 1),
        entry("description", "xyz"),
        entry("value", 1.05),
        entry("enable", true)
    );
Leandro Fantinel
la source
0

Il y en a ImmutableMap.builder()à Guava.

Michal
la source
0

Underscore-java peut construire hashmap.

Map<String, Object> value = U.objectBuilder()
        .add("firstName", "John")
        .add("lastName", "Smith")
        .add("age", 25)
        .add("address", U.arrayBuilder()
            .add(U.objectBuilder()
                .add("streetAddress", "21 2nd Street")
                .add("city", "New York")
                .add("state", "NY")
                .add("postalCode", "10021")))
        .add("phoneNumber", U.arrayBuilder()
            .add(U.objectBuilder()
                .add("type", "home")
                .add("number", "212 555-1234"))
            .add(U.objectBuilder()
                .add("type", "fax")
                .add("number", "646 555-4567")))
        .build();
    // {firstName=John, lastName=Smith, age=25, address=[{streetAddress=21 2nd Street,
    // city=New York, state=NY, postalCode=10021}], phoneNumber=[{type=home, number=212 555-1234},
    // {type=fax, number=646 555-4567}]}
Valentyn Kolesnikov
la source