[英]Is there a Java equivalent of Python's defaultdict?
在 Python 中, defaultdict
類提供了一種從key -> [list of values]
創建映射的便捷方法,在以下示例中,
from collections import defaultdict
d = defaultdict(list)
d[1].append(2)
d[1].append(3)
# d is now {1: [2, 3]}
在 Java 中有與此等效的嗎?
沒有任何東西可以提供開箱即用的默認 dict 行為。 然而,在 Java 中創建您自己的默認 dict 不會那么困難。
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;
}
}
這個類可以像下面這樣使用:
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);
}
此代碼將打印: {1=[2, 3]}
在大多數需要defaultdict
常見情況下,您會更滿意設計合理的 Multimap 或 Multiset,這正是您真正需要的。 Multimap 是鍵 -> 集合映射(默認為空集合),Multiset 是鍵 -> int 映射(默認為零)。
Guava提供了非常好的Multimaps 和 Multisets實現,幾乎涵蓋了所有用例。
但是(這就是我發布新答案的原因)在 Java 8 中,您現在可以使用任何現有Map
復制defaultdict
的其余用例。
getOrDefault()
,顧名思義,返回值(如果存在),或返回默認值。 這不會在地圖中存儲默認值。computeIfAbsent()
從提供的函數中計算一個值(它可以總是返回相同的默認值),並在返回之前將計算出的值存儲在映射中。 如果要封裝這些調用,可以使用 Guava 的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());
}
}
然后像這樣構建你的默認地圖:
Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new);
除了 apache 集合,還檢查谷歌集合:
類似於 Map 的集合,但可以將多個值與單個鍵相關聯。 如果使用相同的鍵但不同的值調用 put(K, V) 兩次,則多重映射包含從鍵到兩個值的映射。
您可以使用Apache Commons 中的MultiMap
。
在 Java 8+ 中,您可以使用:
map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(2);
僅使用 Java 運行時庫,您可以使用HashMap
並添加一個ArrayList
以在鍵尚不存在時保存您的值,或者在鍵存在時將值添加到列表中。
@tendayi-mawushe 的解決方案不適用於原始類型(例如InstantiationException Integer
),這是一種適用於 Integer、Double、Float 的實現。 我經常將 Maps 與這些一起使用並添加靜態構造函數以方便使用
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);
}
}
還有一個測試,為了禮貌:
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"));
}
}
我編寫了包含此類數據結構的庫Guavaberry : DefaultHashMap 。
它經過高度測試和記錄。 您可以通過 Maven Central 輕松找到並集成它。
主要優點是它使用 lambda 來定義工廠方法。 因此,您可以添加一個任意定義的類實例(而不是依賴於默認構造函數的存在):
DefaultHashMap<Integer, List<String>> map = new DefaultHashMap(() -> new ArrayList<>());
map.get(11).add("first");
我希望這能有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.