Tuesday, October 12, 2010

Creating a HashMap with entries

Problem: Create an instance of HashMap in Java with entries in it. I don't want to create a new HashMap instance and keep adding entries to it. I want to have a concise way of creating a HashMap with entries.

Solution: Guava library comes with factory methods that can create a HashMap without using the verbose form of "Map myMap = new HashMap()". You can simply say "Map myMap = newHashMap()", assuming you have done a static import of the Maps.newHashMap method. But that is not sufficient. It would be better to provide a utility method that looks like this: "Map myMap = newHashMapWithEntries(firstKey, firstValue, secondKey, secondValue)". That way it is easy to create static-final maps instead of writing a separate method to populate them or to populate them from constructor, as given below:
public class MyClass { 
   private static final Map<String, URL> serviceUrls = createServiceUrlsMap(); 
   private static Map<String, String> createServiceUrlsMap() { 
    Map<String, URL> retVal = new HashMap<String, URL>(); 
    retVal.put("google", new URL("http://www.google.com"); 
    retVal.put("yahoo", new URL("http://www.yahoo.com"); 
    return retVal; 
   } 
 } 


That is too verbose. What I want is something like:
public class MyClass { 
   private static final Map<String, URL> serviceUrls = newHashMapWithEntries( 
       "google", new URL("http://www.google.com"), 
       "yahoo", new URL("http://www.yahoo.com"));  
 }

So here is one possible implementation of newHashMapWithEntries:
    @SuppressWarnings("unchecked")
    public static <K, V> Map<K, V> newHashMapWithEntries(K firstKey, V firstValue, Object... rest) {
        if(rest.length%2 != 0) {
            throw new IllegalArgumentException("Expected the number of args to be even.");
        }
       
        Class<? extends Object> keyType = firstKey.getClass();
        Class<? extends Object> valueType = firstValue.getClass();
        Map<K, V> retVal = new HashMap<K, V>();
        retVal.put(firstKey, firstValue);
        for(int i = 0; i < rest.length; i += 2) {
            if(!keyType.isAssignableFrom(rest[i].getClass())) {
                throw new IllegalArgumentException("Expected all keys to be of type <? extends " +
                        keyType.getName() + ">, but found " + rest[i].getClass().getName() + ".");
            }
            if(!valueType.isAssignableFrom(rest[i+1].getClass())) {
                throw new IllegalArgumentException("Expected all values to be of type <? extends " +
                        valueType.getName() + ">, but found " + rest[i+1].getClass().getName() + ".");
            }
            retVal.put((K)rest[i], (V)rest[i+1]);
        }
        return retVal;
    }

13 comments:

m.milicevic said...

Hi Roy,
why not use google collections library, e.g:
ImmutableMap map = new ImmutableMap.Builder()
.put("one", 1)
.put("two", 2)
.build();

cheers

Rosarin Roy said...

M.Milicevic: We can use the Google Collections (Guava) as well. Thank you for your suggestion. The one that I had given was a little less verbose than the version using Guava. In case someone is not already using Guava, this implementation would help.

Costantino Cerbo said...

Maybe I'm missing the point.

Why not doing it in this way:
private static final Map serviceUrls = new HashMap();
static {
serviceUrls.put("google", new URL("http://www.google.com"));
serviceUrls.put("yahoo", new URL("http://www.yahoo.com"));
}

(The MalformedURLException must be catched)

jlprat said...

You can also try with Scala, I know, it's not Java, but you can invoke Scala code within Java code.

In Scala you can create Maps this way:

var steps = Map (1 -> "Go straight on", 2 -> "Take the second turn on the left", 3 -> "It's opposite the hairdresser")

m.milicevic said...

hm, what about:
ImmutableMap map = ImmutableMap.of("key", "val", "key1", "val2");

Johan said...

We used a simular approach.
Except, with an extra factory method, the type checks and illegalargumentexception can be removed:

public class MyClass {

public static <K, V> Map<K, V> newHashMapWithEntries(Map.Entry<K, V>... entries) {
Map<K, V> map = new HashMap<K, V>();
if (entries != null) {
for (Entry<K, V> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
}
return map;
}

public static <K, V> Map.Entry<K, V> entry(K k, V v) {
return new AbstractMap.SimpleEntry<K, V>(k, v);
}

public static void main(String[] args) throws Exception {
Map<String, URL> serviceUrls =
newHashMapWithEntries(
entry("google", new URL("http://www.google.com")),
entry("yahoo", new URL("http://www.yahoo.com"))
);
}

}

Fernando said...
This comment has been removed by the author.
Fernando said...
This comment has been removed by the author.
Fernando said...

/* Sorry for all the edits */
final private static Map SERVICE_URLS = Collections.unmodifiableMap(new HashMap() {{
try {
put("google", new URL("http://www.google.com"));
put("yahoo", new URL("http://www.yahoo.com"));
} catch (MalformedURLException ex) {
log.log(Level.ERROR, "", ex);
}
}});

Shekhar Gulati said...

Hello Roy,
You can also use commons-lang3 ArrayUtils toMap function to create a Map
Map map = ArrayUtils.toMap(new Object[][]{{"shekhar",1},{"rahul",2}});

But the problem is Map does not contain type information.It is a map of Objects

Sandeep said...

Great article

Thanks for the information

http://extreme-java.blogspot.com

Javin @ Tibco RV Tutorial said...

Hi,
Thanks for this Nice artilce just to add while discussing about HashMap its worth mentioning following questions which frequently asked in Java interviews now days like How HashMap works in Java or How get() method of HashMap works in JAVA very often. on concept point of view these questions are great and expose the candidate if doesn't know deep details.

Javin
FIX Protocol tutorial

newsletter plugin wordpress said...

The regarded aspects and ideas as mentioned would help students around all those prospects and probabilities which must have been followed by the individual.