29

In short, if you want to write a map of e.g. constants in Java, which in e.g. Python and Javascript you would write as a literal,

T<String,String> CONSTANTS =
{
    "CONSTANT_NAME_0": CONSTANT_VALUE_0 ,
    "CONSTANT_NAME_1": CONSTANT_VALUE_1 ,
    "CONSTANT_NAME_2": CONSTANT_VALUE_2 ,
    //...
} ;

is there a Class or any preset Object that you can use for writing a data structure like that?

FK82
  • 4,907
  • 4
  • 29
  • 42
  • See also [initialize java HashSet values by construction](http://stackoverflow.com/questions/2041778/initialize-java-hashset-values-by-construction) question. – Ilia K. May 30 '12 at 04:27

10 Answers10

53

I like to do it this way:

Map map = new HashMap() {{
    put("foo", "bar");
    put(123, 456);
}};

The double {{ }} are an instance initialization block. They are a bit unusual but they are useful. No need for libraries or helpers.

xpmatteo
  • 11,156
  • 3
  • 26
  • 25
  • 3
    I like this one the best because it requires no external libraries or new classes and it is the shortest. – stand Sep 01 '11 at 20:09
  • 1
    Leads to a missing serialVersionUID warning for me in Eclipse. – Bjorn Jan 04 '12 at 06:29
  • 1
    @BjornTipling I usually turn serialVersionUID warnings off -- I never use the serialization API. – xpmatteo Jan 04 '12 at 20:55
  • 7
    Please note that the double braces contain an instance initializer for *a subclass of HashMap*. I personally don't like to make a subclass for uses like this one. – Jorn Jul 06 '12 at 07:37
  • 1
    I create anonymous subclasses all the time. They are very useful for readability (like here, IMO) – xpmatteo Jul 21 '12 at 17:18
  • 2
    @cfstras That is nonsense. `Map`s compare equal if they ["represent the same mappings"](https://docs.oracle.com/javase/7/docs/api/java/util/Map.html#equals\(java.lang.Object\)). – nemetroid Jul 27 '17 at 09:25
  • @nemetroid You are right. Deleted my comment (was: "this method will create maps that don't compare equal() to other maps"). Don't know where I picked that up. map.getClass() will not be HashMap, but that seems to be the only major difference. – cfstras Jul 31 '17 at 11:33
  • 1
    This is actually *bad* practice. It is not an initializer block (alone). It is creating an anonymous inner class each time. That directly implies consequences on how your equals() method is implemented. Therefore this practice can lead to bizarre problems. This construct is unusual for a reason, and it is kinda bad that you got so many upvotes for it. – GhostCat Jul 31 '18 at 08:44
  • Expanding on previous comments, the outside braces create an anonymous sub-class of `HashMap`. The inner braces are an "Instance Initialization block." Read these official tutorial pages: https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html and https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html. – bitoffdev Jul 25 '19 at 21:12
35

No, Java doesn't have a map literal. The closest you'll come to this is using Google Collections' ImmutableMap:

Map<K,V> CONSTANTS = ImmutableMap.of(
    NAME_1, VALUE_1,
    NAME_2, VALUE_2
    //etc.
  );
Jorn
  • 20,612
  • 18
  • 79
  • 126
  • 1
    I did not say there was. I will look into it. Thanks. – FK82 Sep 26 '10 at 13:57
  • -1 because this uses an external library and is not shorter than xpmatteo's answer. This is also not the closest you will come (answer suggests there are no alternatives that do not depend on an external lib). – Stijn de Witt Feb 14 '12 at 14:37
  • 6
    @Stijn I'd argue Guava should be on your classpath anyway, always. As for writing maps literal style, there *is* no alternative in the Java core libraries. xpmatteo's answer does not qualify as literal style, in my opinion. Having to create a subclass (which is what the double brace initialization really does) for each of these is also less than ideal. – Jorn Feb 15 '12 at 08:33
  • I think the major problem is that the "of" function only accepts maximal 5 key value pairs? – cn123h Feb 14 '17 at 14:56
  • True, but not really a problem: There's `ImmutableMap.builder()` that allows you to make bigger immutable maps. – Jorn Jun 14 '19 at 08:22
15

Constants? I'd use an enum.

public enum Constants { 
    NAME_1("Value1"),
    NAME_2("Value2"),
    NAME_3("Value3");

    private String value;

    Constants(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }
}

Value for e.g. NAME_2 can be obtained as follows:

String name2value = Constants.NAME_2.value();

Only give the enum a bit more sensible name, e.g. Settings, Defaults, etc, whatever those name/value pairs actually represent.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I was thinking about an `enum` myself. However the official documentation on that type is not very informative. Thanks for the example. Your comment on my variable naming -- I might add -- is essentially superfluous however. – FK82 Sep 26 '10 at 16:03
  • You're welcome. The comment was targeted on the enum name `Constants` in the example, not on your code snippet :) – BalusC Sep 26 '10 at 16:05
  • No, the `value()` just returns whatever you've passed into the enum's constructor, in this example `Value1`, `Value2` and `Value3`. The `valueOf()` method is to convert string literals `"NAME_1"`, `"NAME_2"` and `"NAME_3"` to real enum types. Useful when for example mapping a varchar in DB into a Java enum type. – BalusC Sep 26 '10 at 16:44
  • Ok, true. I wasn't reading closely. Anyhow, what do I need this method for? the Name and Value constants are already static members of the `enum` type. You don't need to pass them as arguments trough the constructor, do you? – FK82 Sep 26 '10 at 22:17
  • I mentioned an example: *Useful when for example mapping a varchar in DB into a Java enum type.* Enums are not constructable by yourself. They are already constructed during classloading. There is always ever only one instance of each. The `valueOf()` just returns the `enum` instance as represented by the given `String` name. So, it's useful whenever you have the enum name in flavor of a `String` for some reason (DB column, text file, user input, etc) and want to get a *real* enum instance out of it. E.g. `Constants name1 = Constants.valueOf("NAME_1");` returns `Constants.NAME1`. – BalusC Sep 26 '10 at 22:30
  • Yes. Lookup the ["type safe" pattern](http://java.sun.com/developer/Books/shiftintojava/page1.html), which was used there back in the pre-Java 1.5 ages when Java didn't have the `enum`. It describes roughly how it works under the covers. – BalusC Sep 27 '10 at 13:02
4

Sorry, I'm a tinkerer :-) Here's a somewhat cleaner way.

public class MapTest {
    private static Map<String, String> map;

    static {
        Map<String, String> tmpMap = new HashMap<String, String>();

        tmpMap.put("A", "Apple");
        tmpMap.put("B", "Banana");
        // etc
        map = Collections.unmodifiableMap(tmpMap);
    }

    public Map<String, String> getMap() {
        return map;
    }
}
Tony Ennis
  • 12,000
  • 7
  • 52
  • 73
4

You can write yourself a quick helper function:

@SuppressWarnings("unchecked")
public static <K,V> Map<K,V> ImmutableMap(Object... keyValPair){
    Map<K,V> map = new HashMap<K,V>();

    if(keyValPair.length % 2 != 0){
        throw new IllegalArgumentException("Keys and values must be pairs.");
    }

    for(int i = 0; i < keyValPair.length; i += 2){
        map.put((K) keyValPair[i], (V) keyValPair[i+1]);
    }

    return Collections.unmodifiableMap(map);
}

Note the code above isn't going to stop you from overwriting constants of the same name, using CONST_1 multiple places in your list will result in the final CONST_1's value appearing.

Usage is something like:

Map<String,Double> constants = ImmutableMap(
    "CONST_0", 1.0,
    "CONST_1", 2.0
);
Mark Elliot
  • 75,278
  • 22
  • 140
  • 160
  • I was thinkin about something like that. Might be the next best solution to using an `enum` type in terms of ease of writing. – FK82 Sep 27 '10 at 08:21
1

I like to do the declaration and initialization on the same line. I've used this handy little utility for so long it basically is my "map literal" and until they're done "right" in the languange, I'm gonna continue on using it like that :)

Happy to share it here.

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A handy utility for creating and initializing Maps in a single statement.
 * @author Jonathan Cobb. This source code is in the Public Domain.
 */
public class MapBuilder {

    /**
     * Most common create/init case. Usage:
     *
     *   Map<String, Boolean> myPremadeMap = MapBuilder.build(new Object[][]{
     *     { "a", true }, { "b", false }, { "c", true }, { "d", true },
     *     { "e", "yes, still dangerous but at least it's not an anonymous class" }
     *   });
     *
     * If your keys and values are of the same type, it will even be typesafe:
     *   Map<String, String> someProperties = MapBuilder.build(new String[][]{
     *       {"propA", "valueA" }, { "propB", "valueB" }
     *   });
     *
     * @param values [x][2] array. items at [x][0] are keys and [x][1] are values.
     * @return a LinkedHashMap (to preserve order of declaration) with the "values" mappings
     */
    public static <K,V> Map<K,V> build(Object[][] values) {
        return build(new LinkedHashMap<K,V>(), values);
    }

    /**
     * Usage:
     *  Map<K,V> myMap = MapBuilder.build(new MyMapClass(options),
     *                                    new Object[][]{ {k,v}, {k,v}, ... });
     * @param map add key/value pairs to this map
     * @return the map passed in, now containing new "values" mappings
     */
    public static <K,V> Map<K,V> build(Map<K,V> map, Object[][] values) {
        for (Object[] value : values) {
            map.put((K) value[0], (V) value[1]);
        }
        return map;
    }

    /** Same as above, for single-value maps */
    public static <K,V> Map<K,V> build(Map<K,V> map, K key, V value) {
        return build(map, new Object[][]{{key,value}});
    }

    /**
     * Usage:
     *  Map<K,V> myMap = MapBuilder.build(MyMapClass.class, new Object[][]{ {k,v}, {k,v}, ... });
     * @param mapClass a Class that implements Map
     * @return the map passed in, now containing new "values" mappings
     */
    public static <K,V> Map<K,V> build(Class<? extends Map<K,V>> mapClass, Object[][] values) {

        final Map<K,V> map;
        try { map = mapClass.newInstance(); } catch (Exception e) {
            throw new IllegalStateException("Couldn't create new instance of class: "+mapClass.getName(), e);
        }
        return build(map, values);
    }

    /** Usage: Map<K,V> myMap = MapBuilder.build(key, value); */
    public static <K,V> Map build(K key, V value) {
        Map<K,V> map = new HashMap<>();
        map.put(key, value);
        return map;
    }

}
cobbzilla
  • 1,920
  • 1
  • 16
  • 17
1

Here's another way, best suited for maps that won't be changing:

public class Whatever {
    private static Map<String,String> map = new HashMap<String,String>();

    static {
        map.put("A", "Apple");
        map.put("B", "Banana");
        // etc
    }
}
Tony Ennis
  • 12,000
  • 7
  • 52
  • 73
  • 5
    This doesn't make sure your map can't change at all. You would be better off creating the map in your static block, then assigning the field using Collections.unmodifiableMap() – Jorn Sep 26 '10 at 14:30
  • Correct. I wasn't addressing mutability but using standard Java to make a good stab at a 'constant' map. I'll check out `unmodifiableMap()` - good tip. – Tony Ennis Sep 26 '10 at 14:35
  • `public Map getMap() { return Collections.unmodifiableMap(map); }` seems to allow read access to the map from outside our class. – Tony Ennis Sep 26 '10 at 14:42
  • _maps that won't be changing_ was a poor choice of words. I mean, _maps you have no intention of changing_. – Tony Ennis Sep 27 '10 at 01:12
1

Java7 suppose to implement following syntax:

Map<String, String> = {
    "key1": "value",
    "key2": "value",
    "key3": "value",
    "key4": "value"
};

However now you're forced to use solutions proposed by Jorn or Tony Ennis.

Crozin
  • 43,890
  • 13
  • 88
  • 135
1

Ok, with Jorn's improvement I can't seem to change the map at all, internally or externally. Perhaps not quite as readable, but if you need the map to be unmodifiable I think this is better.

public class MapTest {
    private static Map<String, String> map = initMap();

    private static Map<String, String> initMap() {
        Map<String, String> map = new HashMap<String, String>();

        map.put("A", "Apple");
        map.put("B", "Banana");
        // etc
        return Collections.unmodifiableMap(map);
    }

    public Map<String, String> getMap() {
        return map;
    }

    public static void main(String[] args) {
        MapTest m = new MapTest();
        System.out.println(m.getMap().get("A"));
        m.getMap().put("this", "that");
    }
}
Tony Ennis
  • 12,000
  • 7
  • 52
  • 73
  • Thanks for your answer. I probably have to clarify that, the focus of my question is on the literal writing pattern (known from among others Python and JavaScript), not on immutability. If it was, a `Map` implementation with `static final` fields would probably suffice. – FK82 Sep 26 '10 at 15:38
  • 1
    @FK82: `static final` does not make an immutable map. An immutable map is one where no keys may be added, and no values of existing keys may be changed -- this is not true for a map declared as `static final`. – Mark Elliot Sep 27 '10 at 00:45
  • Point taken. I was however talking about the immutability of the fields in a `class` implementing `Map`. – FK82 Sep 27 '10 at 08:11
1

Since Java 9, you can create unmodifiable maps out of the box -

Map<String, String> emptymap = Map.of();
Map<String, String> map = Map.of("key1", "val1"); 

there are variants supporting upto 10 keypairs. https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html#of()

This is also supported https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html#ofEntries(java.util.Map.Entry...)

 import static java.util.Map.entry;

 Map<Integer,String> map = Map.ofEntries(
     entry(1, "a"),
     entry(2, "b"),
     entry(3, "c"),
     ...
     entry(26, "z"));
mzzzzb
  • 1,422
  • 19
  • 38