Java中有没有类似Python defaultdict的类?

51 投票
9 回答
18890 浏览
提问于 2025-04-15 16:19

在Python中,defaultdict类让我们可以很方便地创建一个从键 -> [值的列表]的映射。下面这个例子就是这样做的:

from collections import defaultdict
d = defaultdict(list)
d[1].append(2)
d[1].append(3)
# d is now {1: [2, 3]}

在Java中有没有类似的东西呢?

9 个回答

9

除了Apache的集合库,您还可以看看Google的集合库

这个集合和Map(映射)有点像,但它可以让一个键对应多个值。如果你用同一个键但不同的值调用put(K, V)两次,那么这个多重映射就会把这个键和两个值都关联起来。

14

在大多数情况下,如果你想要一个 defaultdict,其实你会发现一个设计得当的多重映射(Multimap)或多重集合(Multiset)会更让你满意,这才是你真正需要的。多重映射是指一个键对应一个集合(默认是一个空集合),而多重集合是指一个键对应一个整数(默认是零)。

Guava 提供了非常不错的多重映射和多重集合的实现,几乎可以满足所有的使用场景。

但是(这也是我为什么要发这个新回答),在 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);
34

默认字典(default dict)在其他编程语言中没有现成的功能。不过,在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]}

撰写回答