【问题标题】:Extend HashMap to return empty HashSet for non-found keys扩展 HashMap 以为未找到的键返回空 HashSet
【发布时间】:2015-09-04 14:43:34
【问题描述】:

我想创建一个包含 HashSet 作为值的 HashMap,并在找不到键时返回一个空的 HashSet。

public class IsbnHashMap<K,V> extends HashMap<K,V> {
    protected V defaultValue;
    public IsbnHashMap(V defaultValue) {
        this.defaultValue = defaultValue;
    }
    @Override
    public V get(Object k) {
        return containsKey(k) ? super.get(k) : defaultValue;
    }
}

但是我的实现不起作用。

private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>();

这将返回“无法应用 HashSet”。如果我尝试将K,V 中的IsbnHashMap 更改为&lt;String, HashSet&lt;String&gt;&gt;,我也会遇到一些时髦的错误。我该如何实现?

【问题讨论】:

  • 你没有向构造函数传递任何东西......(老实说,我建议不要这样做 - 它显着改变了开发人员对 Map 的期望,但这是一个单独的问题。)
  • 您的合同(返回空 HashSet)与您的实现不一致,因为 (a) 无法保证构造函数中的对象为空,更重要的是 (b) 一旦它被返回为由于未找到查找的结果,接收方可以向其添加数据
  • 强烈建议为此使用适当设计的库,例如 Guava 的 Multimap,它可以正确地完成所有这些操作而不会违反接口契约。

标签: java hashmap hashset


【解决方案1】:

首先应该注意的是,在 Java-8 中你可以使用:

isbnToId.computeIfAbsent(isbn, k -> new HashSet<>()).add(_id);

其次,如果你真的想在以前的 Java 版本中做这样的事情,你最好为此目的创建单独的方法(例如,getOrDefault()),以免违反合同。第三,您需要为每个新密钥创建new HashSet&lt;&gt;()。如果您返回相同的实例,它将在给定的键之间共享。如果您不希望用户修改它,最好使用不可修改的Collections.emptySet() 作为默认值。这样用户可以安全地执行isbnToId.getOrDefault(isbn).contains(_id),但尝试isbnToId.getOrDefault(isbn).add(_id) 会导致异常。如果要支持修改(Java-8 之前),例如,可以将元素类传递给构造函数:

public static class MyMap<K, V> extends HashMap<K, V> {
    private Class<?> clazz;

    public MyMap(Class<?> clazz) {
        this.clazz = clazz;
    }

    public V getOrCompute(K key) {
        V v = get(key);
        if(v == null) {
            try {
                v = (V) clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            put(key, v);
        }
        return v;
    }
}

使用示例:

MyMap<String, Set<String>> map = new MyMap<>(HashSet.class);
map.getOrCompute("a").add("b");
map.getOrCompute("a").add("c");
map.getOrCompute("d").add("e");
System.out.println(map); // {a=[b, c], d=[e]}

这里我们假设使用默认构造函数实例化传递的类是可以的。另一种方法是传递能够生成默认值的工厂接口。

【讨论】:

    【解决方案2】:

    正如乔恩·斯基特所说...

    private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>(new HashSet<String>());
    

    ...但是,这将返回与 Dunni 指出的相同的默认对象。

    这样就可以了:

    private static HashMap<String, HashSet<String>> isbnToId = new HashMap<String, HashSet<String>>();
    
    public static void coupleIsbnToId(String isbn, String _id) {
        if (!isbnToId.containsKey(isbn)) {
            isbnToId.put(isbn, new HashSet<String>());
        }
        isbnToId.get(isbn).add(_id);
    }
    

    【讨论】:

    • 只是 FTR:如果你这样做,它可能会产生一些令人讨厌的后果(例如,如果你获得默认值 HashSet,然后在应用程序中对其进行操作,它当然会反映默认值value HashSet,因为它仍然是同一个对象)。所以要小心这样做的后果。
    猜你喜欢
    • 2011-11-23
    • 2015-03-05
    • 1970-01-01
    • 2016-03-18
    • 2010-12-10
    • 2018-03-02
    • 1970-01-01
    相关资源
    最近更新 更多