38

I have a Map<String, List<String>> in my code, where I'd avoid potential null pointers if the map's #get() method returned an empty list instead of null. Is there anything like this in the java API? Should I just extend HashMap?

10
  • 8
    using the decorator pattern would probably be better than extending HashMap... Commented Jan 28, 2011 at 21:50
  • 1
    I don't doubt you, but why would it be better? Commented Jan 28, 2011 at 21:52
  • 7
    @jk: Why would you want to limit yourself to it always being a HashMap? What if sometimes you wanted a LinkedHashMap? Or a ConcurrentHashMap? Or a TreeMap? Basically, favour composition over inheritance :) Commented Jan 28, 2011 at 21:54
  • 1
    @jk: I strongly recommend using a Guava Multimap as suggested by @Stephen C. I think this is what you really want, rather than a general purpose Map that returns a non-null value when it doesn't contain a key. Commented Jan 29, 2011 at 2:07
  • 2
    This is a duplicate of stackoverflow.com/questions/7519339/… Commented Dec 24, 2012 at 8:58

6 Answers 6

40

Thanks to default methods, Java 8 now has this built in with Map::getOrDefault:

Map<Integer, String> map = ...
map.put(1, "1");
System.out.println(map.getOrDefault(1, "2")); // "1"
System.out.println(map.getOrDefault(2, "2")); // "2"
Sign up to request clarification or add additional context in comments.

5 Comments

This is the slimmest answer as it doesn't require Guava. And, by the way, it was what I was looking for. Upvoting.
Looks like based on timing this is the best solution, but came way late to the party (thanks java 8!). I like its simplicity. +1
Should be the accepted answer now that Java 8 is commonplace.
This is useful, but actually the OP asked for the map's #get method to return the value, not use another method. These things are not the same =)
This does not respond the original question - the OP wants to "avoid potential null pointers". The implementation of getOrDefault is more or less like if (get(key) != null) || containsKey(key)) return get(key) else return defaultValue;. So if the map contains the key, but the value is null, you will not avoid the null pointer!
25

@Jon's answer is a good way to do what you are asking directly.

But is strikes me that what you might be trying to implement is a "multimap"; i.e. a mapping from a key to a collection of values. If that is the case, then you should also look at the multimap classes in Guava or Apache commons collections.

Look at:

2 Comments

+1 And get() on a Multimap that doesn't contain the key returns an empty Collection by contract... exactly what the OP wants. The same isn't true of the Apache Commons MultiMap which has unspecified behavior.
@ColinD True. When using apache commons, we can use DefaultedMap, which returns a default value when the map does not contain the specified value.
24

As noted in comments:

Guava's computing map concept was superseded with LoadingCache. Also java 8 introduced to Map interface nice computeIfAbsent default method which doesn't break map contract and features lazy evaluation .


Guava had the idea of a "computing map" which will execute a function to provide a value if it's not present. It was implemented in MapMaker.makeComputingMap; you could now use CacheBuilder - see CacheBuilder.build for more details.

It may be overkill for what you're after - you might be better off just writing a Map implementation which has a Map (using composition rather than extending any particular implementation) and then just return the default if it's not present. Every method other than get could probably just delegate to the other map:

public class DefaultingMap<K, V> implements Map<K, V>
{
    private final Map<K, V> map;
    private final V defaultValue;

    public DefaultingMap(Map<K, V> map, V defaultValue)
    {
        this.map = map;
        this.defaultValue = defaultValue;
    }

    @Override public V get(Object key)
    {
        V ret = map.get(key);
        if (ret == null)
        {
            ret = defaultValue;
        }
        return ret;
    }

    @Override public int size()
    {
        return map.size();
    }

    // etc
}

8 Comments

Beware: doing only this breaks the contract for get(): that if the map doesn't contain the key, null is returned. To be conformant you should override containsKey() to always return true. I would assume makeComputingMap() would do that as well but didn't find it stated explicitly in the docs.
@Mark: Possibly. Although then you get into trickiness around iterating over the keys, as well... basically sooner or later the abstraction breaks. Where you break it would definitely be a matter for deep consideration :)
The // etc is not small, and makes this solution impractical IMO.
Alternately you could extend com.google.common.collect.ForwardingMap (from Google Guava) which makes the implementation @JonSkeet suggested much less verbose.
Guava's computing map concept was superseded with LoadingCache. Also java 8 introduced to Map interface nice computeIfAbsent default method which doesn't break map contract and features lazy evaluation
|
11

Similar to previous posts except you can just override the get method if you want to change its behaviour.

Map<String, List<String>> map = new LinkedHashMap<String, List<String>>() {
    public String get(Object key) {
        List<String> list = super.get(key);
        if (list == null && key instanceof String)
           super.put(key, list = new ArrayList<String>());
        return list;
    }
}; 

Comments

8

Guava has a method to do exactly what you want. It is similar to Argote's answer.

Map<String, List<String>> myMap = ...
Functions.forMap(myMap, Arrays.asList("default", "value"));

5 Comments

Very nice one-liner. :D
Maybe I'm missing something, but how does this create a Map implementation that has a default value?
It doesn't. It creates a Guava Function, which is similar to a Map. The Map interface doesn't allow for default values.
Ok, I thought that there was a leap of logic somewhere that I wasn't getting. The OP is asking specifically for an implementation of Map, and you say that this does "exactly what you want".
What the OP wants breaks Map's contract, so the OP wants something that isn't a Map. :)
0

It seems to me that you can simply write a wrapper function to get the value you want and if it's null return the default instead.

Map<String, List<String>> myMap = new Map<String, List<String>>();

public List<String> myGet(String key) {
    List<String> ret = myMap.get(key);
    if(ret == NULL) {
        return defaultList(); // where defaultList is a function that generates your default list.
    }
    return ret;
}

This assumes that your myMap is a global variable (for simplicity's sake), if that's not what you want then you can also extend Map or HashMap into something like MyMap and just include this extra function. That way you can use it from any place where a MyMap object is instantiated.

1 Comment

This doesn't help much if you need to pass the map to something expecting a Map.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.