Is there a Java equivalent of Python's defaultdict?

13,904

Solution 1

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]}

Solution 2

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);

Solution 3

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.

Solution 4

You can use MultiMap from Apache Commons.

Solution 5

In Java 8+ you can use:

map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(2);
Share:
13,904
gatoatigrado
Author by

gatoatigrado

see website (zoratung.com)

Updated on June 17, 2022

Comments

  • gatoatigrado
    gatoatigrado about 2 years

    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?

  • Mark Byers
    Mark Byers over 14 years
  • Lambda Fairy
    Lambda Fairy over 12 years
    Instead of using a Class, you can also try passing a Guava Supplier -- see docs.guava-libraries.googlecode.com/git-history/v10.0/javado‌​c/…
  • Soulman
    Soulman over 11 years
    Or, if you don't want the Guava dependeny, just define your own Supplier<V> interface in DefaultDict.
  • sam boosalis
    sam boosalis about 10 years
    i would prefer constructing the DefaultDict with my own value: public DefaultDict(V value) { this.value = value; }
  • Ar5hv1r
    Ar5hv1r over 6 years
    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.
  • antak
    antak about 6 years
    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.
  • Ar5hv1r
    Ar5hv1r about 6 years
    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.
  • dtk
    dtk about 5 years
    The MultiMap is Deprecated. since 4.1, use MultiValuedMap instead. commons.apache.org/proper/commons-collections/apidocs/org/…
  • user102008
    user102008 almost 5 years
    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.
  • rocksNwaves
    rocksNwaves almost 3 years
    How would this work if your default value type was something other than a collection? For instance an Integer with value of 1?
  • rocksNwaves
    rocksNwaves almost 3 years
    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.