Java 中是否有相当于 Python 的 defaultdict 的工具?

问题描述 投票:0回答:9

在 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 中有与此等效的吗?

java python hashmap
9个回答
34
投票

没有任何东西可以提供开箱即用的默认字典的行为。然而,用 Java 创建你自己的默认字典并不那么困难。

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


14
投票

在大多数常见情况下,如果您想要

defaultdict
,您会对正确设计的 Multimap 或 Multiset 感到更满意,这正是您真正需要的。 Multimap 是键 -> 集合映射(默认为空集合),而 Multiset 是键 -> int 映射(默认为零)。

Guava提供了非常好的Multimaps和Multisets的实现,这将涵盖几乎所有用例。

但是(这就是我发布新答案的原因)使用 Java 8,您现在可以使用任何现有的

defaultdict
复制
Map
的剩余用例。

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

9
投票

除了 apache 集合之外,还可以检查 google 集合

类似于Map的集合,但它可以将多个值与单个键相关联。如果使用相同的键但不同的值调用 put(K, V) 两次,则多重映射包含从键到两个值的映射。


8
投票

在 Java 8+ 中你可以使用:

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

6
投票

您可以使用 Apache Commons

 中的 
MultiMap


1
投票

仅使用 Java 运行时库,您可以使用

HashMap
并添加
ArrayList
来在键尚不存在时保存值,或者在键存在时将值添加到列表中。


1
投票

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

0
投票

从java8+开始,您可以使用map.computeIfAbsent。 例如:

    List food = new ArrayList<String>();
    food.add("Pizza");
    food.add("Chicken");
    Map<String, List<String>> favouriteFood = new HashMap<>();
    favouriteFood.put("Mike", food);
    favouriteFood.computeIfAbsent("James", name -> new ArrayList<>())
            .add("Hamburger");
Mike -> [Pizza, Chicken]
James -> [Hamburger]

-1
投票

我编写了包含这样的数据结构的库GuavaberryDefaultHashMap

它经过严格测试和记录。您可以通过 Maven Central 轻松找到并集成它。

主要优点是它使用lambda来定义工厂方法。因此,您可以添加任意定义的类实例(而不是依赖于默认构造函数的存在):

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

希望对您有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.