builder per HashMap


Risposte:


20

Poiché l' Mapinterfaccia Java 9 contiene:

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

I limiti di questi metodi di fabbrica sono che:

  • non può contenere nulls come chiavi e / o valori (se devi memorizzare valori nulli dai un'occhiata alle altre risposte)
  • produrre mappe immutabili

Se abbiamo bisogno di una mappa mutabile (come HashMap) possiamo usare il suo costruttore di copia e lasciarlo copiare il contenuto della mappa creata tramiteMap.of(..)

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );

2
Si noti che i metodi Java 9 non consentono nullvalori, il che può essere un problema a seconda del caso d'uso.
Per Lundberg

@JoshM. IMO Map.of(k1,v1, k2,v2, ...)può essere utilizzato in sicurezza quando non abbiamo molti valori. Per quantità maggiori di valori Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)ci fornisce un codice più leggibile che è meno soggetto a errori (a meno che non ti abbia frainteso).
Pshemo

Hai capito bene. Il primo è davvero disgustoso per me; Mi rifiuto di usarlo!
Josh M.

164

Non esiste nulla di simile per HashMaps, ma puoi creare una ImmutableMap con un builder:

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

E se hai bisogno di una mappa modificabile, puoi semplicemente fornirla al costruttore HashMap.

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

43
ImmutableMapnon supporta i nullvalori. Quindi c'è una limitazione di questo approccio: non puoi impostare valori nel tuo HashMapto null.
vitaly

5
sean-patrick-floyd Bene, un esempio pratico: NamedParameterJdbcTemplate di Spring si aspetta una mappa di valori codificati dai nomi dei parametri. Diciamo che voglio usare NamedParameterJdbcTemplate per impostare un valore di colonna su null. Non vedo: a) come sia un codice odore; b) come utilizzare il pattern di oggetti nulli qui
vitale

2
@vitaly non può discutere con questo
Sean Patrick Floyd

2
C'è qualcosa di sbagliato nell'usare il new HashMapcostruttore Java invece del Maps.newHashMapmetodo statico ?
Coray,

1
@CorayThan - Jonik ha ragione, è solo una scorciatoia che si basa sull'inferenza del tipo statico. stackoverflow.com/a/13153812
AndersDJohnson

46

Non proprio un builder, ma usando un inizializzatore:

Map<String, String> map = new HashMap<String, String>() {{
    put("a", "1");
    put("b", "2");
}};

Aspettare. Non sarebbe map instanceof HashMapfalso? Sembra un'idea non così eccezionale.
Elazar Leibovich

3
@Elazar map.getClass()==HashMap.classrestituirà false. Ma è comunque un test stupido. HashMap.class.isInstance(map)dovrebbe essere preferito e ciò restituirà vero.
Sean Patrick Floyd

59
Detto questo: penso ancora che questa soluzione sia malvagia.
Sean Patrick Floyd

11
Questo è un inizializzatore di istanza, non un inizializzatore statico. Viene eseguito dopo il costruttore del super, ma prima del corpo del costruttore, per ogni costruttore della classe. Il ciclo di vita non è molto noto e quindi evito questo idioma.
Joe Coder

14
Questa è una pessima soluzione e dovrebbe evitare: stackoverflow.com/a/27521360/3253277
Alexandre Dubreuil

36

Questo è simile alla risposta accettata, ma un po 'più pulita, a mio avviso:

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

Esistono diverse varianti del metodo precedente e sono ottime per creare mappe statiche, immutabili e immutabili.


4
Ho chiesto un costruttore. Sei limitato a una manciata di elementi.
Elazar Leibovich

Bello e pulito, ma mi fa desiderare l'operatore => di Perl ... che è una sensazione strana.
Aaron Maenpaa

10

Eccone uno molto semplice ...

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

poi

import static FluentHashMap.map;

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

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


Mi piace la semplicità del tuo approccio. Soprattutto dal momento che questo è il 2017 (quasi il 2018 ora!) E non esiste ancora tale API nel JDK
Milad Naseri

Sembra davvero fantastico, grazie. @MiladNaseri è assurdo che JDK non abbia ancora qualcosa del genere nella sua API, che vergogna.
improbabile

9

Un semplice generatore di mappe è banale da scrivere:

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

6

Puoi usare:

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

Non è così elegante e leggibile, ma fa il lavoro.


1
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2);, meglio?
lschin

Come il builder, ma con una quantità limitata di dati, poiché è implementato con sovraccarichi. Se hai solo pochi elementi, immagino sia preferibile.
Elazar Leibovich

4

HashMapè mutevole; non c'è bisogno di un costruttore.

Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);

E se volessi inizializzare un campo con esso? Tutta la logica nella stessa riga è migliore della logica sparsa tra campo e c'tor.
Elazar Leibovich

@Elazar: Se vuoi inizializzare un campo con valori specifici noti in fase di compilazione in questo modo, di solito vuoi che quel campo sia immutabile e dovresti usarlo ImmutableSet. Se vuoi davvero che sia modificabile, puoi inizializzarlo nel costruttore o in un blocco inizializzatore di istanza o in un blocco inizializzatore statico se è un campo statico.
ColinD

1
Ehm, avrei dovuto dirlo ImmutableMapovviamente.
ColinD

Non credo proprio. Preferirei vedere l'inizializzazione nella stessa linea di definizione, quindi averli nascosti in un'inizializzazione non statica {{init();}}(non nel costruttore, poiché un altro costruttore potrebbe dimenticarlo). Ed è bello che sia una specie di azione atomica. Se la mappa è volatile, inizializzarla con un builder assicurarsi che sia sempre nullo nello stato finale, mai riempita a metà.
Elazar Leibovich

1

Puoi utilizzare l'API fluente nelle raccolte 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);

Ecco un blog con maggiori dettagli ed esempi.

Nota: sono un committer per le collezioni Eclipse.


0

Qualche tempo fa avevo un requisito simile. Non ha nulla a che fare con Guava ma puoi fare qualcosa del genere per essere in grado di costruire in modo pulito Mapun costruttore fluente.

Crea una classe base che estenda 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;
    }
}

Quindi crea il costruttore fluente con metodi adatti alle tue esigenze:

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

}

Puoi quindi implementarlo in questo modo:

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

0

Questo è qualcosa che ho sempre desiderato, soprattutto durante la configurazione dei dispositivi di prova. Alla fine, ho deciso di scrivere un mio semplice builder fluente in grado di costruire qualsiasi implementazione di 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;
    }

0

Eccone uno che ho scritto

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

Lo usi in questo modo:

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

0

Utilizzando java 8:

Questo è un approccio di 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);
    }
}

Come usare:

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


0

Underscore-java può creare 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}]}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.