Builder für HashMap

109

Guava bietet uns großartige Factory-Methoden für Java-Typen, wie z Maps.newHashMap().

Aber gibt es auch Builder für Java Maps?

HashMap<String,Integer> m = Maps.BuildHashMap.
    put("a",1).
    put("b",2).
    build();
Elazar Leibovich
quelle
1
Schauen Sie sich minborgsjavapot.blogspot.com/2014/12/…
Tioma

Antworten:

20

Da Java 9- MapSchnittstelle enthält:

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

Einschränkungen dieser Fabrikmethoden sind, dass sie:

  • kann nulls nicht als Schlüssel und / oder Werte halten (wenn Sie Nullen speichern müssen, schauen Sie sich andere Antworten an)
  • produzieren unveränderlich Karten

Wenn wir eine veränderbare Karte benötigen (wie HashMap), können wir ihren Kopierkonstruktor verwenden und den Inhalt der über erstellten Karte kopieren lassenMap.of(..)

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
Pshemo
quelle
2
Beachten Sie, dass die Java 9-Methoden keine nullWerte zulassen , was je nach Anwendungsfall ein Problem sein kann.
Per Lundberg
@ JoshM. IMO Map.of(k1,v1, k2,v2, ...)kann sicher verwendet werden, wenn wir nicht viele Werte haben. Bei einer größeren Anzahl von Werten erhalten Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)wir besser lesbaren Code, der weniger fehleranfällig ist (es sei denn, ich habe Sie falsch verstanden).
Pshemo
Du hast es gut verstanden. Ersteres ist für mich einfach nur ekelhaft; Ich weigere mich, es zu benutzen!
Josh M.
164

Für HashMaps gibt es so etwas nicht, aber Sie können eine ImmutableMap mit einem Builder erstellen:

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

Und wenn Sie eine veränderbare Map benötigen, können Sie diese einfach dem HashMap-Konstruktor zuführen.

final Map<String, Integer> m = Maps.newHashMap(
    ImmutableMap.<String, Integer>builder().
        put("a", 1).
        put("b", 2).
        build());
Sean Patrick Floyd
quelle
43
ImmutableMapunterstützt keine nullWerte. Daher gibt es eine Einschränkung dieses Ansatzes: Sie können keine Werte in Ihrem HashMapto festlegen null.
Vitaly
5
sean-patrick-floyd Nun, ein praktisches Beispiel: Spring's NamedParameterJdbcTemplate erwartet eine Karte von Werten, die durch Parameternamen verschlüsselt sind. Angenommen, ich möchte NamedParameterJdbcTemplate verwenden, um einen Spaltenwert auf null zu setzen. Ich sehe nicht: a) wie es ein Codegeruch ist; b) wie man hier ein Nullobjektmuster verwendet
vitaly
2
@vitaly kann damit nicht streiten
Sean Patrick Floyd
2
Ist etwas falsch daran, den new HashMapJava-Konstruktor anstelle der statischen Maps.newHashMapMethode zu verwenden?
CorayThan
1
@CorayThan - Jonik ist korrekt, es ist nur eine Verknüpfung, die auf statischer Typinferenz beruht. stackoverflow.com/a/13153812
AndersDJohnson
46

Nicht ganz ein Builder, aber mit einem Initialisierer:

Map<String, String> map = new HashMap<String, String>() {{
    put("a", "1");
    put("b", "2");
}};
Johan Sjöberg
quelle
Warten. Würde das nicht map instanceof HashMapfalsch machen ? Sieht nach einer nicht so tollen Idee aus.
Elazar Leibovich
3
@Elazar map.getClass()==HashMap.classgibt false zurück. Aber das ist trotzdem ein dummer Test. HashMap.class.isInstance(map)sollte bevorzugt werden, und das wird wahr zurückgeben.
Sean Patrick Floyd
59
Das heißt: Ich denke immer noch, dass diese Lösung böse ist.
Sean Patrick Floyd
11
Dies ist ein Instanzinitialisierer, kein statischer Initialisierer. Es wird nach dem Konstruktor des Supers, aber vor dem Körper des Konstruktors für jeden Konstruktor in der Klasse ausgeführt. Der Lebenszyklus ist nicht sehr bekannt und deshalb vermeide ich diese Redewendung.
Joe Coder
14
Dies ist eine sehr schlechte Lösung und sollte vermieden werden: stackoverflow.com/a/27521360/3253277
Alexandre DuBreuil
36

Dies ähnelt der akzeptierten Antwort, ist aber meiner Ansicht nach etwas sauberer:

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

Es gibt verschiedene Variationen der oben genannten Methode, und sie eignen sich hervorragend zum Erstellen statischer, unveränderlicher und unveränderlicher Karten.

Jake Toronto
quelle
4
Ich bat um einen Baumeister. Sie sind auf eine Handvoll Elemente beschränkt.
Elazar Leibovich
Schön und sauber, aber ich sehne mich nach Perls => Operator ... was ein seltsames Gefühl ist.
Aaron Maenpaa
10

Hier ist eine sehr einfache ...

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

dann

import static FluentHashMap.map;

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

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

Kulmat
quelle
Ich mag die Einfachheit Ihres Ansatzes. Zumal dies 2017 ist (fast 2018 jetzt!) Und es noch keine solche API im JDK gibt
Milad Naseri
Sieht echt cool aus, danke. @MiladNaseri Es ist verrückt, dass JDK so etwas noch nicht in seiner API hat, was für eine verdammte Schande.
unwahrscheinlich
9

Ein einfacher Kartenersteller ist trivial zu schreiben:

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());
        }
    }
}
Agnes
quelle
6

Sie können verwenden:

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

Es ist nicht so edel und lesbar, macht aber die Arbeit.

Elazar Leibovich
quelle
1
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2);, besser?
lschin
Wie der Builder, jedoch mit begrenzter Datenmenge, da er mit Überlastungen implementiert ist. Wenn Sie nur wenige Elemente haben - ich denke, es ist vorzuziehen.
Elazar Leibovich
4

HashMapist veränderlich; Es ist kein Baumeister erforderlich.

Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
ColinD
quelle
Was ist, wenn Sie ein Feld damit initialisieren möchten? Alle Logik in derselben Zeile ist besser als die zwischen Feld und c'tor verstreute Logik.
Elazar Leibovich
@Elazar: Wenn Sie ein Feld mit bestimmten Werten initialisieren möchten, die zur Kompilierungszeit wie folgt bekannt sind, möchten Sie normalerweise, dass dieses Feld unveränderlich ist und verwendet werden sollte ImmutableSet. Wenn Sie wirklich möchten, dass es veränderbar ist, können Sie es im Konstruktor oder in einem Instanzinitialisierungsblock oder einem statischen Initialisierungsblock initialisieren, wenn es sich um ein statisches Feld handelt.
ColinD
1
Ähm, hätte ImmutableMapdort offensichtlich sagen sollen .
ColinD
Das glaube ich nicht. Ich würde die Intialisierung lieber in derselben Definitionslinie sehen und sie dann in eine nicht statische Intialisierung stecken lassen{{init();}} (nicht im Konstruktor, da andere Konstruktoren dies möglicherweise vergessen). Und es ist schön, dass es eine Art atomare Aktion ist. Wenn die Karte flüchtig ist, stellen Sie bei der Initialisierung mit einem Builder sicher, dass sie immer entweder nulloder im Endzustand ist und niemals zur Hälfte gefüllt ist.
Elazar Leibovich
1

Sie können die fließende API in Eclipse-Sammlungen verwenden :

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

Hier ist ein Blog mit mehr Details und Beispielen.

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.

Donald Raab
quelle
0

Ich hatte vor einiger Zeit eine ähnliche Anforderung. Es hat nichts mit Guave zu tun, aber Sie können so etwas tun, um eine sauber zu konstruierenMap mit einem fließenden Builder eine .

Erstellen Sie eine Basisklasse, die Map erweitert.

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

Erstellen Sie dann den fließenden Builder mit Methoden, die Ihren Anforderungen entsprechen:

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

}

Sie können es dann folgendermaßen implementieren:

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

Das wollte ich schon immer, besonders beim Einrichten von Testgeräten. Schließlich entschied ich mich, einen einfachen, fließenden Builder zu schreiben, der jede Map-Implementierung erstellen kann - 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
quelle
0

Hier ist eine, die ich geschrieben habe

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

Sie verwenden es so:

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
quelle
0

Verwenden von Java 8:

Dies ist ein Ansatz von 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);
    }
}

Wie benutzt man:

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
quelle
0

Es gibt ImmutableMap.builder()in Guave.

Michal
quelle
0

Unterstrich-Java kann Hashmap erstellen.

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
quelle