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.