Is there a Java equivalent of Python's defaultdict?
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);
Comments
-
gatoatigrado about 2 years
In Python, the
defaultdict
class provides a convenient way to create a mapping fromkey -> [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 over 14 years
-
Lambda Fairy over 12 yearsInstead of using a
Class
, you can also try passing a GuavaSupplier
-- see docs.guava-libraries.googlecode.com/git-history/v10.0/javadoc/… -
Soulman over 11 yearsOr, if you don't want the Guava dependeny, just define your own
Supplier<V>
interface inDefaultDict
. -
sam boosalis about 10 yearsi would prefer constructing the
DefaultDict
with my own value:public DefaultDict(V value) { this.value = value; }
-
Ar5hv1r over 6 yearsIt would be more usable if instead of extending from
HashMap
you usedForwardingMap
and allowed the caller to specify the backing map. Prefer composition to inheritance. -
antak about 6 yearsI didn't downvote. Good idea all in all. However I don't think you can name your variable
default
, and I thinkcreate(V)
is just asking users to shoot themselves in their foot. -
Ar5hv1r about 6 yearsThanks, 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 about 5 yearsThe
MultiMap
isDeprecated. since 4.1, use MultiValuedMap instead
. commons.apache.org/proper/commons-collections/apidocs/org/… -
user102008 almost 5 yearsWouldn't this behave incorrectly if you stored a value of
null
for a certain key into the map? When you callget()
, it would behave as if the key didn't exist (even though it does exist, as determined bycontainsKey()
), and they create a new value and overwrite the existing value in the map. -
rocksNwaves almost 3 yearsHow would this work if your default value type was something other than a collection? For instance an
Integer
with value of 1? -
rocksNwaves almost 3 yearsAlso, 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.