简体   繁体   English

是否有 Java 等价于 Python 的 defaultdict?

[英]Is there a Java equivalent of Python's defaultdict?

In Python, the defaultdict class provides a convenient way to create a mapping from key -> [list of values] , in the following example,在 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]}

Is there an equivalent to this in Java?在 Java 中有与此等效的吗?

There is nothing that gives the behaviour of default dict out of the box.没有任何东西可以提供开箱即用的默认 dict 行为。 However creating your own default dict in Java would not be that difficult.然而,在 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;
    }    
}

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]}此代码将打印: {1=[2, 3]}

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.在大多数需要defaultdict常见情况下,您会更满意设计合理的 Multimap 或 Multiset,这正是您真正需要的。 A Multimap is a key -> collection mapping (default is an empty collection) and a Multiset is a key -> int mapping (default is zero). Multimap 是键 -> 集合映射(默认为空集合),Multiset 是键 -> int 映射(默认为零)。

Guava provides very nice implementations of both Multimaps and Multisets which will cover almost all use cases. Guava提供了非常好的Multimaps 和 Multisets实现,几乎涵盖了所有用例。

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 .但是(这就是我发布新答案的原因)在 Java 8 中,您现在可以使用任何现有Map复制defaultdict的其余用例。

  • getOrDefault() , as the name suggests, returns the value if present, or returns a default value. getOrDefault() ,顾名思义,返回值(如果存在),或返回默认值。 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. computeIfAbsent()从提供的函数中计算一个值(它可以总是返回相同的默认值),在返回之前将计算出的值存储在映射中。

If you want to encapsulate these calls you can use Guava's ForwardingMap :如果要封装这些调用,可以使用 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());
  }
}

Then construct your default map like so:然后像这样构建你的默认地图:

Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new);

in addition to apache collections, check also google collections :除了 apache 集合,还检查谷歌集合

A collection similar to a Map, but which may associate multiple values with a single key.类似于 Map 的集合,但可以将多个值与单个键相关联。 If you call put(K, V) twice, with the same key but different values, the multimap contains mappings from the key to both values.如果使用相同的键但不同的值调用 put(K, V) 两次,则多重映射包含从键到两个值的映射。

您可以使用Apache Commons 中的MultiMap

在 Java 8+ 中,您可以使用:

map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(2);

仅使用 Java 运行时库,您可以使用HashMap并添加一个ArrayList以在键尚不存在时保存您的值,或者在键存在时将值添加到列表中。

The solution from @tendayi-mawushe did not work for me with Primitive types (eg InstantiationException Integer ), here is one implementation that works with Integer, Double, Float. @tendayi-mawushe 的解决方案不适用于原始类型(例如InstantiationException Integer ),这是一种适用于 Integer、Double、Float 的实现。 I often use Maps with these and added static constructors for conveninence我经常将 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);
    }
}

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

I wrote the library Guavaberry containing such data structure: DefaultHashMap .我编写了包含此类数据结构的库GuavaberryDefaultHashMap

It is highly tested and documented.它经过高度测试和记录。 You can find it and integrate it pretty easily via Maven Central.您可以通过 Maven Central 轻松找到并集成它。

The main advatage is that it uses lambda to define the factory method.主要优点是它使用 lambda 来定义工厂方法。 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.我希望这能有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM