130

Guava provides us with great factory methods for Java types, such as Maps.newHashMap().

But are there also builders for java Maps?

HashMap<String,Integer> m = Maps.BuildHashMap.
    put("a",1).
    put("b",2).
    build();
MMascarin
  • 468
  • 4
  • 6
Elazar Leibovich
  • 32,750
  • 33
  • 122
  • 169
  • 1
    Look at http://minborgsjavapot.blogspot.com/2014/12/java-8-initializing-maps-in-smartest-way.html – Tioma Dec 01 '17 at 13:24

15 Answers15

186

There is no such thing for HashMaps, but you can create an ImmutableMap with a builder:

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

And if you need a mutable map, you can just feed that to the HashMap constructor.

final Map<String, Integer> m = Maps.newHashMap(
    ImmutableMap.<String, Integer>builder().
        put("a", 1).
        put("b", 2).
        build());
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 53
    `ImmutableMap` does not support `null` values. So there is limitation of this approach: you cannot set values in your `HashMap` to `null`. – vitaly Jun 04 '13 at 07:56
  • @vitaly true, but a) mapping null values is a code smell anyway and b) you can always map a dedicated [null object](http://en.wikipedia.org/wiki/Null_Object_pattern) as placeholder – Sean Patrick Floyd Jun 04 '13 at 08:00
  • 6
    sean-patrick-floyd Well, one practical example: Spring's [NamedParameterJdbcTemplate](http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.html) expects map of values keyed by parameter names. Let's say I want to use NamedParameterJdbcTemplate to set a column value to null. I don't see: a) how it is a code smell; b) how to use null object pattern here – vitaly Jun 06 '13 at 10:27
  • 3
    @vitaly can't argue with that – Sean Patrick Floyd Jun 07 '13 at 08:22
  • 2
    Is there anything wrong with using the `new HashMap` Java constructor instead of the static `Maps.newHashMap` method? – CorayThan Oct 28 '13 at 19:49
  • @CorayThan: not at all; that way it's just slightly more verbose with the duplicated type params. – Jonik Feb 17 '14 at 19:52
  • 1
    @CorayThan - Jonik is correct, it's just a shortcut relying on static type inference. http://stackoverflow.com/a/13153812 – AndersDJohnson Sep 20 '15 at 15:35
  • 1
    Watch out for this before you spend a lot of time trying to get it to work, and then find out they don't accept null values. – orbfish Dec 27 '16 at 00:16
  • @orbfish and what is the point of accepting null values? Sounds like you should be using some other data structure – Sean Patrick Floyd Dec 27 '16 at 10:48
  • Syntax changed? `final Map m = new ImmutableMap.Builder() .put("one", 1) .build();` – Anand Rockzz Sep 12 '17 at 04:47
  • @AnandRockzz nothing new, you just have the choice btw constructor and factory method. Effective Java says factory method is nicer, and I agree – Sean Patrick Floyd Sep 12 '17 at 15:30
  • I posted [answer](https://stackoverflow.com/a/56383519) about `Map.of(k1,v1, k2,v2, ...)` and `Map.ofEntries(Map.entry(k1,v2), Map.entry(k2,v2), ..)` which ware added in Java 9 but IMO it would be better if it was part this (accepted) answer. If you also think that way feel free to update your answer and ping me so I would remove mine. – Pshemo May 30 '19 at 18:18
  • 1
    @Pshemo no, let's leave it as it is. My answer was the correct one 8 years ago, yours is the correct one now :-) – Sean Patrick Floyd May 30 '19 at 22:10
  • Having `null` values in a map is not a "code smell" it's completely legit, and ridiculous that Java doesn't allow it (in some cases). – Josh M. Mar 25 '21 at 13:18
  • @JoshM. I'm gonna go with the Google opinion on this one: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained – Sean Patrick Floyd Mar 25 '21 at 16:06
  • Love it but it has a drawback of not overriding keys. `ImmutableMap.builder().putAll(Map.of("a", "valA")).put("a", "valAOverride").put("b", "valB").build()` causes `Multiple entries with same key: a=valAOverride and a=valA`. – AlikElzin-kilaka Nov 17 '21 at 11:54
52

Not quite a builder, but using an initializer:

Map<String, String> map = new HashMap<String, String>() {{
    put("a", "1");
    put("b", "2");
}};
Peter Crotty
  • 301
  • 2
  • 9
Johan Sjöberg
  • 47,929
  • 21
  • 130
  • 148
  • Wait. Wouldn't that make `map instanceof HashMap` false? Looks like a not-so-great idea. – Elazar Leibovich Sep 08 '11 at 09:43
  • 3
    @Elazar `map.getClass()==HashMap.class` will return false. But that's a stupid test anyway. `HashMap.class.isInstance(map)` should be preferred, and that will return true. – Sean Patrick Floyd Sep 08 '11 at 09:50
  • @Johan, indeed I meant `map.getClass`. Sorry. I agree that checking with `getClass` is not a great idea, but it's still have a chance to be a nasty bug. (BTW thanks for teaching me about DBI). – Elazar Leibovich Sep 08 '11 at 09:52
  • 12
    This is an instance initializer, not a static initializer. It is run after the super's constructor, but before the body of the constructor, for every constructor in the class. The lifecycle is not very well-known and so I avoid this idiom. – Joe Coder May 10 '12 at 00:23
  • 18
    This is a very bad solution and should avoided: http://stackoverflow.com/a/27521360/3253277 – Alexandre DuBreuil Aug 31 '15 at 16:48
  • 1
    +1, as is this was easily the most "convenient" solution I could find (that didn't suffer from the drawback of `ImmutableMap` not supporting `null`), but it's important to understand the significant downsides that prevent it from being usable in any production code. However, I see no downside to using it in test or scratch code to create a map quickly. – Matsu Q. Apr 07 '21 at 20:31
49

Since Java 9 Map interface contains:

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

Limitations of those factory methods are that they:

  • can't hold nulls as keys and/or values (if you need to store nulls take a look at other answers)
  • produce immutable maps

If we need mutable map (like HashMap) we can use its copy-constructor and let it copy content of map created via Map.of(..)

Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • 2
    Note that the Java 9 methods do not allow `null` values, which can be a problem depending on the use case. – Per Lundberg Jul 10 '19 at 08:36
  • @JoshM. IMO `Map.of(k1,v1, k2,v2, ...)` can be used safely when we don't have many values. For larger amount of values `Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)` gives us more readable code which is less error prone (unless I misunderstood you). – Pshemo Oct 16 '19 at 20:41
  • You understood fine. The former is just really gross to me; I refuse to use it! – Josh M. Oct 17 '19 at 12:01
38

This is similar to the accepted answer, but a little cleaner, in my view:

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

There are several variations of the above method, and they are great for making static, unchanging, immutable maps.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
Jake Toronto
  • 3,524
  • 2
  • 23
  • 27
13

Here is a very simple one ...

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

then

import static FluentHashMap.map;

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

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

culmat
  • 1,156
  • 11
  • 12
  • I like the simplicity of your approach. Especially since this is 2017 (almost 2018 now!), and there is still no such API in the JDK – Milad Naseri Dec 08 '17 at 06:27
  • Looks really cool, thanks. @MiladNaseri it's insane that JDK doesn't have something like this in its API still, what a fricking shame. – improbable Jan 16 '20 at 16:24
10

A simple map builder is trivial to write:

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());
        }
    }
}
MMascarin
  • 468
  • 4
  • 6
Agnes
  • 654
  • 6
  • 11
7

You can use:

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

It's not as classy and readable, but does the work.

MMascarin
  • 468
  • 4
  • 6
Elazar Leibovich
  • 32,750
  • 33
  • 122
  • 169
4

HashMap is mutable; there's no need for a builder.

Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
ColinD
  • 108,630
  • 30
  • 201
  • 202
  • 1
    What if you want to initialize a field with it? All logic in the same line is better than logic scattered between field and c'tor. – Elazar Leibovich Sep 08 '11 at 13:25
  • @Elazar: If you want to initialize a field with specific values that are known at compile time like this, you _usually_ want that field to be immutable and should use `ImmutableSet`. If you really want it to be mutable, you could initialize it in the constructor or an instance initializer block or a static initializer block if it's a static field. – ColinD Sep 08 '11 at 15:06
  • 1
    Er, should have said `ImmutableMap` there obviously. – ColinD Sep 08 '11 at 17:35
  • 1
    I don't think so. I'd rather see intialization in the same line of definition, then having them tucked in a non-static intialization `{{init();}}` (not in the constructor, since other constructor might forget it). And it's nice that it's sort-of atomic action. If map is volatile, then intializing it with a builder ensure it's always either `null` or in the final state, never half filled. – Elazar Leibovich Sep 09 '11 at 06:13
3

Using java 8:

This is a approach of 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);
    }
}

How to Use:

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

There's ImmutableMap.builder() in Guava.

Michal
  • 384
  • 3
  • 8
1

I had a similar requirement a while back. Its nothing to do with Guava but you can do something like this to be able to cleanly construct a Map using a fluent builder.

Create a base class that extends 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;
    }
}

Then create the fluent builder with methods that suit your needs:

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

}

You can then implement it like this:

ValueMap map = new ValueMap()
      .withValue("key 1", "value 1")
      .withValue("key 2", "value 2")
      .withValue("key 3", "value 3")
tarka
  • 5,289
  • 10
  • 51
  • 75
1

Here's one I wrote

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

You use it like this:

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
  • 185,044
  • 174
  • 569
  • 824
1

You can use the fluent API in 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);

Here's a blog with more detail and examples.

Note: I am a committer for Eclipse Collections.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
0

This is something I always wanted, especially while setting up test fixtures. Finally, I decided to write a simple fluent builder of my own that could build any Map implementation - 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
  • 156
  • 2
  • 15
0

Underscore-java can build 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
  • 2,029
  • 1
  • 24
  • 31