51

In Python, the defaultdict class provides a convenient way to create a mapping from key -> [list of values], in the following example,

from collections import defaultdict
d = defaultdict(list)
d[1].append(2)
d[1].append(3)
# d is now {1: [2, 3]}

Is there an equivalent to this in Java?

Georgy
  • 12,464
  • 7
  • 65
  • 73
gatoatigrado
  • 16,580
  • 18
  • 81
  • 143

8 Answers8

34

There is nothing that gives the behaviour of default dict out of the box. However creating your own default dict in Java would not be that difficult.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class DefaultDict<K, V> extends HashMap<K, V> {

    Class<V> klass;
    public DefaultDict(Class klass) {
        this.klass = klass;    
    }

    @Override
    public V get(Object key) {
        V returnValue = super.get(key);
        if (returnValue == null) {
            try {
                returnValue = klass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            this.put((K) key, returnValue);
        }
        return returnValue;
    }    
}

This class could be used like below:

public static void main(String[] args) {
    DefaultDict<Integer, List<Integer>> dict =
        new DefaultDict<Integer, List<Integer>>(ArrayList.class);
    dict.get(1).add(2);
    dict.get(1).add(3);
    System.out.println(dict);
}

This code would print: {1=[2, 3]}

Tendayi Mawushe
  • 25,562
  • 6
  • 51
  • 57
  • 5
    Instead of using a `Class`, you can also try passing a Guava `Supplier` -- see http://docs.guava-libraries.googlecode.com/git-history/v10.0/javadoc/com/google/common/base/Supplier.html – Lambda Fairy Oct 03 '11 at 01:29
  • 3
    Or, if you don't want the Guava dependeny, just define your own `Supplier` interface in `DefaultDict`. – Soulman Nov 27 '12 at 13:41
  • i would prefer constructing the `DefaultDict` with my own value: `public DefaultDict(V value) { this.value = value; }` – sam boosalis Jun 07 '14 at 01:43
  • Wouldn't this behave incorrectly if you stored a value of `null` for a certain key into the map? When you call `get()`, it would behave as if the key didn't exist (even though it *does* exist, as determined by `containsKey()`), and they create a new value and overwrite the existing value in the map. – user102008 Aug 22 '19 at 02:29
  • How would this work if your default value type was something other than a collection? For instance an `Integer` with value of 1? – rocksNwaves Sep 15 '21 at 14:30
  • Also, it seems that this results in an unsafe type cast that cannot be made safe because of type erasure. I'd roll with it, but my boss certainly wouldn't. – rocksNwaves Sep 15 '21 at 15:52
  • @Tendayi Mawushe How to iterate over `[2, 3]` for each key i.e. `1`? – Learner Aug 01 '23 at 17:42
14

In most common cases where you want a defaultdict, you'll be even happier with a properly designed Multimap or Multiset, which is what you're really looking for. A Multimap is a key -> collection mapping (default is an empty collection) and a Multiset is a key -> int mapping (default is zero).

Guava provides very nice implementations of both Multimaps and Multisets which will cover almost all use cases.

But (and this is why I posted a new answer) with Java 8 you can now replicate the remaining use cases of defaultdict with any existing Map.

  • getOrDefault(), as the name suggests, returns the value if present, or returns a default value. This does not store the default value in the map.
  • computeIfAbsent() computes a value from the provided function (which could always return the same default value) and does store the computed value in the map before returning.

If you want to encapsulate these calls you can use Guava's ForwardingMap:

public class DefaultMap<K, V> extends ForwardingMap<K, V> {
  private final Map<K, V> delegate;
  private final Supplier<V> defaultSupplier;

  /**
   * Creates a map which uses the given value as the default for <i>all</i>
   * keys. You should only use immutable values as a shared default key.
   * Prefer {@link #create(Supplier)} to construct a new instance for each key.
   */
  public static DefaultMap<K, V> create(V defaultValue) {
    return create(() -> defaultValue);
  }

  public static DefaultMap<K, V> create(Supplier<V> defaultSupplier) {
    return new DefaultMap<>(new HashMap<>(), defaultSupplier);
  }

  public DefaultMap<K, V>(Map<K, V> delegate, Supplier<V> defaultSupplier) {
    this.delegate = Objects.requireNonNull(delegate);
    this.defaultSupplier = Objects.requireNonNull(defaultSupplier);
  }

  @Override
  public V get(K key) {
    return delegate().computeIfAbsent(key, k -> defaultSupplier.get());
  }
}

Then construct your default map like so:

Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new);
dimo414
  • 47,227
  • 18
  • 148
  • 244
  • I didn't downvote. Good idea all in all. However I don't think you can name your variable `default`, and I think `create(V)` is just asking users to shoot themselves in their foot. – antak May 21 '18 at 09:58
  • Thanks, you're absolutely right. I agree `create(V)` is a little risky, but I suspect it'd be missed if it was absent. I've added a comment, at least, to call out the risk. – dimo414 May 21 '18 at 20:38
9

in addition to apache collections, check also google collections:

A collection similar to a Map, but which may associate multiple values with a single key. If you call put(K, V) twice, with the same key but different values, the multimap contains mappings from the key to both values.

dfa
  • 114,442
  • 31
  • 189
  • 228
8

In Java 8+ you can use:

map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(2);
garg10may
  • 5,794
  • 11
  • 50
  • 91
6

You can use MultiMap from Apache Commons.

dimo414
  • 47,227
  • 18
  • 148
  • 244
Mirek Pluta
  • 7,883
  • 1
  • 32
  • 23
  • 1
    Link: http://commons.apache.org/collections/api/org/apache/commons/collections/MultiMap.html – Mark Byers Nov 23 '09 at 21:46
  • The `MultiMap` is `Deprecated. since 4.1, use MultiValuedMap instead`. https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/MultiMap.html – dtk May 29 '19 at 07:33
1

Using just the Java runtime library you could use a HashMap and add an ArrayList to hold your values when the key does not exist yet or add the value to the list when the key does exist.

rsp
  • 23,135
  • 6
  • 55
  • 69
1

The solution from @tendayi-mawushe did not work for me with Primitive types (e.g. InstantiationException Integer), here is one implementation that works with Integer, Double, Float. I often use Maps with these and added static constructors for conveninence

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

/** Simulate the behaviour of Python's defaultdict */
public class DefaultHashMap<K, V> extends HashMap<K, V> {
    private static final long serialVersionUID = 1L;

    private final Class<V> cls;
    private final Number defaultValue;

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public DefaultHashMap(Class factory) {
        this.cls = factory;
        this.defaultValue = null;
    }

    public DefaultHashMap(Number defaultValue) {
        this.cls = null;
        this.defaultValue = defaultValue;
    }

    @SuppressWarnings("unchecked")
    @Override
    public V get(Object key) {
        V value = super.get(key);
        if (value == null) {
            if (defaultValue == null) {
                try {
                    value = cls.newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = (V) defaultValue;
            }
            this.put((K) key, value);
        }
        return value;
    }

    public static <T> Map<T, Integer> intDefaultMap() {
        return new DefaultHashMap<T, Integer>(0);
    }

    public static <T> Map<T, Double> doubleDefaultMap() {
        return new DefaultHashMap<T, Double>(0d);
    }

    public static <T> Map<T, Float> floatDefaultMap() {
        return new DefaultHashMap<T, Float>(0f);
    }

    public static <T> Map<T, String> stringDefaultMap() {
        return new DefaultHashMap<T, String>(String.class);
    }
}

And a test, for good manners:

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.junit.Test;

public class DefaultHashMapTest {

    @Test
    public void test() {
        Map<String, List<String>> dm = new DefaultHashMap<String, List<String>>(
                ArrayList.class);
        dm.get("nokey").add("one");
        dm.get("nokey").add("two");
        assertEquals(2, dm.get("nokey").size());
        assertEquals(0, dm.get("nokey2").size());
    }

    @Test
    public void testInt() {
        Map<String, Integer> dm = DefaultHashMap.intDefaultMap();
        assertEquals(new Integer(0), dm.get("nokey"));
        assertEquals(new Integer(0), dm.get("nokey2"));
        dm.put("nokey", 3);
        assertEquals(new Integer(0), dm.get("nokey2"));
        dm.put("nokey3", 3);
        assertEquals(new Integer(3), dm.get("nokey3"));
    }

    @Test
    public void testString() {
        Map<String, String> dm = DefaultHashMap.stringDefaultMap();
        assertEquals("", dm.get("nokey"));
        dm.put("nokey1", "mykey");
        assertEquals("mykey", dm.get("nokey1"));
    }
}
Renaud
  • 16,073
  • 6
  • 81
  • 79
-1

I wrote the library Guavaberry containing such data structure: DefaultHashMap.

It is highly tested and documented. You can find it and integrate it pretty easily via Maven Central.

The main advatage is that it uses lambda to define the factory method. So, you can add an arbitrarly defined instance of a class (instead of relying on the existence of the default constructor):

DefaultHashMap<Integer, List<String>> map = new DefaultHashMap(() -> new ArrayList<>());
map.get(11).add("first");

I hope that can be of help.

user967489
  • 39
  • 2
  • It would be more usable if instead of extending from `HashMap` you used `ForwardingMap` and allowed the caller to specify the backing map. Prefer composition to inheritance. – dimo414 Mar 10 '18 at 01:46