builder dla HashMap


Odpowiedzi:


20

Ponieważ Mapinterfejs Java 9 zawiera:

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

Ograniczenia tych metod fabrycznych polegają na tym, że:

  • nie może przechowywać nulls jako kluczy i / lub wartości (jeśli chcesz przechowywać wartości null, spójrz na inne odpowiedzi)
  • tworzyć niezmienne mapy

Jeśli potrzebujemy mapy mutowalnej (takiej jak HashMap), możemy użyć jej konstruktora kopiującego i pozwolić mu kopiować zawartość mapy utworzonej przezMap.of(..)

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

2
Należy zauważyć, że metody Java 9 nie dopuszczają nullwartości, co może stanowić problem w zależności od przypadku użycia.
Per Lundberg,

@JoshM. IMO Map.of(k1,v1, k2,v2, ...)może być bezpiecznie używane, gdy nie mamy wielu wartości. W przypadku większej ilości wartości Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)daje nam bardziej czytelny kod, który jest mniej podatny na błędy (chyba że źle cię zrozumiałem).
Pshemo

Dobrze zrozumiałeś. To pierwsze jest dla mnie po prostu naprawdę obrzydliwe; Nie chcę tego używać!
Josh M.

164

Nie ma czegoś takiego w przypadku HashMaps, ale możesz utworzyć ImmutableMap za pomocą kreatora:

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

A jeśli potrzebujesz mutowalnej mapy, możesz po prostu przesłać ją do konstruktora HashMap.

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

43
ImmutableMapnie obsługuje nullwartości. Jest więc ograniczenie tego podejścia: nie możesz ustawić wartości w swoim HashMapto null.
vitaly

5
sean-patrick-floyd Cóż, jeden praktyczny przykład: Spring's NamedParameterJdbcTemplate oczekuje mapy wartości wpisanych przez nazwy parametrów. Powiedzmy, że chcę użyć NamedParameterJdbcTemplate, aby ustawić wartość kolumny na null. Nie rozumiem: a) jaki to zapach kodu; b) jak tutaj używać wzorca obiektu zerowego
Vitaly

2
@vitaly nie może się z tym kłócić
Sean Patrick Floyd,

2
Czy jest coś złego w używaniu new HashMapkonstruktora Java zamiast Maps.newHashMapmetody statycznej ?
CorayThan

1
@CorayThan - Jonik ma rację, to tylko skrót polegający na wnioskowaniu o typie statycznym. stackoverflow.com/a/13153812
AndersDJohnson

46

Niezupełnie konstruktor, ale używając inicjatora:

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

Czekać. Czy to nie map instanceof HashMapfałsz? Wygląda na niezbyt dobry pomysł.
Elazar Leibovich

3
@Elazar map.getClass()==HashMap.classzwróci false. Ale to i tak głupi test. HashMap.class.isInstance(map)powinno być preferowane, a to zwróci wartość true.
Sean Patrick Floyd

59
To powiedziawszy: nadal uważam, że to rozwiązanie jest złe.
Sean Patrick Floyd

11
To jest inicjator wystąpienia, a nie inicjator statyczny. Jest uruchamiany po konstruktorze super, ale przed treścią konstruktora, dla każdego konstruktora w klasie. Cykl życia nie jest zbyt dobrze znany, więc unikam tego idiomu.
Joe Coder

14
To bardzo złe rozwiązanie i należy go unikać: stackoverflow.com/a/27521360/3253277
Alexandre DuBreuil

36

Jest to podobne do przyjętej odpowiedzi, ale moim zdaniem trochę jaśniejsze:

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

Istnieje kilka odmian powyższej metody, które świetnie nadają się do tworzenia statycznych, niezmiennych, niezmiennych map.


4
Poprosiłem o budowniczego. Jesteś ograniczony do kilku elementów.
Elazar Leibovich

Ładnie i czysto, ale tęsknię za operatorem Perla => ... co jest dziwnym uczuciem.
Aaron Maenpaa

10

Oto bardzo prosty ...

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

następnie

import static FluentHashMap.map;

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

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


Podoba mi się prostota twojego podejścia. Zwłaszcza, że ​​jest rok 2017 (teraz prawie 2018!), A takiego API nadal nie ma w JDK
Milad Naseri

Wygląda naprawdę fajnie, dzięki. @MiladNaseri to szaleństwo, że JDK nie ma jeszcze czegoś takiego w swoim API, co za cholerny wstyd.
nieprawdopodobne

9

Prosty kreator map jest trywialny do napisania:

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

Możesz użyć:

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

Nie jest tak elegancki i czytelny, ale działa.


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

To samo co konstruktor, ale z ograniczoną ilością danych, ponieważ jest implementowany z przeciążeniami. Jeśli masz tylko kilka elementów - myślę, że lepiej.
Elazar Leibovich

4

HashMapjest zmienny; nie ma potrzeby budowania.

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

Co jeśli chcesz zainicjować nim pole? Cała logika w tej samej linii jest lepsza niż logika rozproszona między polem a c'tor.
Elazar Leibovich

@Elazar: Jeśli chcesz zainicjować pole z określonymi wartościami, które są znane w czasie kompilacji, w ten sposób, zwykle chcesz, aby to pole było niezmienne i powinno być używane ImmutableSet. Jeśli naprawdę chcesz, aby był mutowalny, możesz zainicjować go w konstruktorze lub bloku inicjatora wystąpienia lub statycznym bloku inicjatora, jeśli jest to pole statyczne.
ColinD,

1
Eee, powinienem był ImmutableMaptam oczywiście powiedzieć .
ColinD,

Nie sądzę. Wolałbym raczej widzieć inicjalizację w tej samej linii definicji, a następnie umieszczać je w niestatycznej inicjalizacji {{init();}}(nie w konstruktorze, ponieważ inny konstruktor może o tym zapomnieć). I fajnie, że to coś w rodzaju atomowej akcji. Jeśli mapa jest niestabilna, zainicjuj ją z konstruktorem, aby upewnić się, że jest zawsze albo nullw stanie końcowym, nigdy w połowie wypełniona.
Elazar Leibovich

1

Możesz korzystać z płynnego API w Eclipse Collections :

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

Oto blog zawierający więcej szczegółów i przykładów.

Uwaga: jestem promotorem Eclipse Collections.


0

Miałem podobny wymóg jakiś czas temu. To nie ma nic wspólnego z guawą, ale możesz zrobić coś takiego, aby móc czysto skonstruować Mapużywając płynnego budowniczego.

Utwórz klasę bazową, która rozszerza 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;
    }
}

Następnie stwórz płynnego kreatora za pomocą metod, które odpowiadają Twoim potrzebom:

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

}

Następnie możesz to zaimplementować w następujący sposób:

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

0

To jest coś, czego zawsze chciałem, szczególnie podczas konfigurowania urządzeń testowych. W końcu zdecydowałem się napisać własnego, prostego, płynnego konstruktora, który mógłby zbudować dowolną implementację mapy - 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

Oto jeden, który napisałem

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

Używasz tego w ten sposób:

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

Korzystanie z java 8:

To jest podejście 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);
    }
}

Jak używać:

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 może budować 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}]}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.