93

I am sick of the following pattern:

value = map.get(key);
if (value == null) {
    value = new Object();
    map.put(key, value);
}

This example only scratches the surface of the extra code to be written when you have nested maps to represent a multi-dimensional structure.

Is there some Java method to avoid this?

2
  • Out of curiosity, the Object you want to put, is it just an Object, or will the type vary? Also, is it already created or should it only be created if no object already exists? Commented Dec 2, 2011 at 7:34
  • The type is known at compile time. Usually it's a String to Map (to Map)* to Integer. Commented Dec 16, 2011 at 16:47

7 Answers 7

80

The

java.util.concurrent.ConcurrentMap 

and from Java 8

Java.util.Map

has

putIfAbsent(K key, V value) 

which returns the existing value, and if that is null inserts given value. So if no value exists for key returns null and inserts the given value, otherwise returns existing value

If you need lazy evaluation of the value there is

computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
Sign up to request clarification or add additional context in comments.

6 Comments

Returns: the previous value associated with the specified key, or null if there was no mapping for the key
Note that this forces you to create a new object, even if there is already one available in the map. If you really only create a new Object(), this may be negligible.
@user802421 is right--this will return null if the value is inserted.
java.util.Map has putIfAbsent from JDK 8, not only ConcurrentMap.
it doesn't work is the value itself is supposed to be a collection. as what I wanted is add an empty collection AND RETURN IT so that I can add items into that collection.
|
78

Java 8 adds nice method to the Map: compute, computeIfPresent, computeIfAbsent

To achieve what you need:

Object existingOrCreated = map.computeIfAbsent(key, (k) -> new Object());

1 Comment

The accepted answer points to this same method. However, this answer makes it clearer.
10

The problem with this pattern is that you'd have to somehow define the value that should be used in case the get() returns null.

There certainly are libraries out there and IIRC there are also some newer collections that do that, but unfortunately I don't remember which those were.

However, you could write a utility method yourself, provided you have a standard way of creating the new values. Something like this might work:

public static <K, V> V safeGet(K key, Map<K,V> map, Class<V> valueClass) throws /*add exceptions*/ {
  V value = map.get(key);
  if( value == null ) {
    value = valueClass.newInstance();
    map.put( key, value );
  }   

  return value;
} 

Note that you'd either have to throw the reflection exceptions or handle them in the method. Additionally, this requires the valueClass to provide a no-argument constructor. Alternatively, you could simply pass the default value that should be used.

Java 8 update

It has already been mentioned in other answers but for the sake of completeness I'll add the information here as well.

As of Java 8 there is the default method computeIfAbsent(key, mappingFunction) which basically does the same, e.g. if the value class was BigDecimal it could look like this:

BigDecimal value = map.computeIfAbsent(key, k -> new BigDecimal("123.456"));

The implementation of that method is similar to the safeGet(...) defined above but more flexible, directly available at the map instance and better tested. So when possible I'd recommend using computeIfAbsent() instead.

Comments

4

You can use MutableMap and getIfAbsentPut() from Eclipse Collections which returns the value mapped to the key or inserts the given value and returns the given value if no value is mapped to the key.

You can either use a method reference to create a new Object:

MutableMap<String, Object> map = Maps.mutable.empty();
Object value = map.getIfAbsentPut("key", Object::new);

Or you can directly create a new Object:

MutableMap<String, Object> map = Maps.mutable.empty();    
Object value = map.getIfAbsentPut("key", new Object());

In the first example, the object will be created only if there is no value mapped to the key.

In the second example, the object will be created regardless.

Note: I am a contributor to Eclipse Collections.

Comments

2

If in any case you need to get a default data in your map if it's not existing

map.getOrDefault(key, defaultValue);

javadocs

2 Comments

Interesting, but this doesn't put it in the map as the OP asked
Note: MAP.getOrDefault(key, defaultValue) is supported only on java 8 and above.
1

EDIT : Note that the feature mentioned below is long deprecated, and a CacheBuilder should be used instead.

The Guava library has a "computing map", see MapMaker.makeComputingMap(Function).

Map<String, Object> map = new MapMaker().makeComputingMap(
    new Function<String, Object>() {
      public String apply(Stringt) {
        return new Object();
      }
  });

If you need the Function several times, extract it into a utility class, and then create the Map like this (where MyFunctions.NEW_OBJECT is the static Function instance):

Map<String, Object> map = new MapMaker()
    .makeComputingMap(MyFunctions.NEW_OBJECT);

2 Comments

MapMaker from Google Guava is dead. Ref: code.google.com/p/guava-libraries/wiki/MapMakerMigration
I understand the Guava people think an autocomputing Map.get() is bad, because it breaks the Map contract. If you use the computing map only internally, it should be easy to use a LoadingCache instead. I can see their point of a catastrophe waiting to happen when sharing a computing map with unsuspecting code. For that use case, one should use a safeGet() utility function like the one given by Thomas in his answer.
0

Maybe I'm not seeing the whole problem, but how about using inheritance or composition to add this behavior to the Map object?

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.