builder dla HashMap

109

Guava zapewnia nam świetne metody fabryczne dla typów Java, takich jak Maps.newHashMap().

Ale czy są też konstruktorzy map Java?

HashMap<String,Integer> m = Maps.BuildHashMap.
    put("a",1).
    put("b",2).
    build();
Elazar Leibovich
źródło

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") );
Pshemo
źródło
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());
Sean Patrick Floyd
źródło
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");
}};
Johan Sjöberg
źródło
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.

Jake Toronto
źródło
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

culmat
źródło
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());
        }
    }
}
Agnieszka
źródło
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.

Elazar Leibovich
źródło
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);
ColinD
źródło
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.

Donald Raab
źródło
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")
tarka
źródło
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;
    }
aathif
źródło
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();
Dónal
źródło
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)
    );
Leandro Fantinel
źródło
0

Jest ImmutableMap.builder()w guawie.

Michał
źródło
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}]}
Valentyn Kolesnikov
źródło