HashMap返回未找到的键的默认值?


151

是否可以为HashMap集合中未找到的所有键返回默认值?


您可以检查密钥是否存在并返回默认值。或扩展类并修改行为。甚至您都可以使用null-并在要使用它的任何地方进行检查。
SudhirJ 2011年

2
这是相关的/ stackoverflow.com/questions/4833336/的副本...在此讨论了其他一些选项。
Mark Butler

3
查看Map API getOrDefault() 链接
Trey Jonn

Answers:


136

[更新]

正如其他答案和评论者所指出的那样,从Java 8开始,您可以简单地调用Map#getOrDefault(...)

[原版的]

没有Map实现可以完全做到这一点,但是通过扩展HashMap来实现自己的实现很简单:

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

20
准确地说,您可能希望将条件从调整(v == null)(v == null && !this.containsKey(k)),以防他们有意添加了一个null值。我知道,这只是一个小案例,但作者可能会遇到。
亚当·佩恩特

@maerics:我注意到您曾经使用过!this.containsValue(null)。这与稍有不同!this.containsKey(k)。该containsValue如果一些解决失败重点已经明确分配的值null。例如:map = new HashMap(); map.put(k1, null); V v = map.get(k2);在这种情况下,v仍然null正确吗?
亚当·佩恩特

21
总的来说,我认为这是一个坏主意-我会将默认行为推送给客户端,或者将一个不声称自己是Map的委托发送给客户端。特别是,缺少有效的keySet()或entrySet()会导致任何问题,这些问题都希望遵守Map协定。而containsKey()所隐含的无限有效密钥集可能会导致难以诊断的不良性能。不过,并不是说它可能无法达到某些特定目的。
Ed Staub

这种方法的一个问题是,如果值是一个复杂的对象。Map <String,List> #put将无法正常工作。
Eyal

在ConcurrentHashMap上不起作用。在那里,您应该检查get的结果是否为null。
dveim

162

在Java 8中,使用Map.getOrDefault。它需要密钥,如果找不到匹配的密钥,则返回值。


14
getOrDefault非常好,但是每次访问地图时都需要使用默认定义。创建静态值映射时,一次定义默认值也将具有可读性。
2014年

3
实现自己很简单。private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
杰克·萨特里亚诺

@JackSatriano是的,但是您必须对默认值进行硬编码,或者为其使用静态变量。
Blrp

1
请参见下面的使用computeIfAbsent的答案,当默认值昂贵或每次均应不同时,效果更好。
hectorpal

尽管这对于内存来说更糟,但只有在默认值构造/计算昂贵的情况下,才会节省计算时间。如果价格便宜,您可能会发现它的性能较差,因为它必须插入地图中,而不仅仅是返回默认值。当然可以。
Spycho

73

如果您不想重新发明轮子,请使用Commons的DefaultedMap,例如,

Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname"); 
// surname == "[NO ENTRY FOUND]"

如果您首先不负责创建地图,则也可以传递现有地图。


26
+1尽管有时比重引入对小部分简单功能的依赖要容易,但重塑轮子比重更容易。
maerics 2011年

3
有趣的是,我合作过的许多项目的类路径中都已经有类似的东西(Apache Commons或Google Guava)
bartosz.r 2013年

@ bartosz.r,绝对不在移动设备上
Pacerier

44

Java 8 在接口中引入了一个不错的validateIfAbsent默认方法,Map该方法存储延迟计算的值,因此不会破坏映射协定:

Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));

来源:http//blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/

免责声明: 此答案与OP要求的内容不完全匹配,但在某些情况下,如果键数受到限制并且缓存不同的值将有利可图,则可以很方便地匹配问题的标题。不应在带有大量键和相同默认值的相反情况下使用它,因为这会不必要地浪费内存。


OP没问过什么:他希望地图上没有副作用。同样,为每个缺少的键存储默认值也将浪费存储空间。
numéro6

@numéro6,是的,这与OP要求的不完全相同,但是一些使用Google搜索功能的人仍然认为此副答案很有用。就像其他答案提到的那样,要不违反地图契约就不可能完全实现OP的要求。此处未提及的另一个解决方法是使用另一个抽象代替Map
Vadzim '17年

在不违反地图合同的情况下,可以准确地实现OP的要求。不需要任何解决方法,只使用getOrDefault是正确的(最新)方法,而用ifIfIfAbsent是错误的方法:您将浪费时间通过存储结果(每个缺失的键)来调用mappingFunction和内存。我看不出有什么很好的理由代替getOrDefault。我要描述的是Map合约上有两种不同方法的确切原因:不应混淆两个不同的用例(我不得不在工作中进行一些修复)。这个答案分散了混乱。
numéro6


10

您可以简单地创建一个继承HashMap的新类并添加getDefault方法。这是一个示例代码:

public class DefaultHashMap<K,V> extends HashMap<K,V> {
    public V getDefault(K key, V defaultValue) {
        if (containsKey(key)) {
            return get(key);
        }

        return defaultValue;
    }
}

我认为您不应在实现中覆盖get(K key)方法,这是由于Ed Staub在其评论中指定的原因,并且因为您将破坏Map接口的合同(这可能会导致一些难以发现的问题)错误)。


4
您有一个要点是不要重写该get方法。另一方面,您的解决方案不允许通过接口使用类,这种情况经常会发生。
KrzysztofJabłoński2014年


3

默认情况下会这样做。它返回null


@Larry,HashMap对于这种功能,当我null完全满意的时候,我似乎有些愚蠢。
mrkhrts 2011年

15
但是,如果您使用的是NullObject模式,或者不想在整个代码中分散空检查,那是不对的,我完全理解这一愿望。
戴夫·牛顿


1

我发现LazyMap很有帮助。

当使用映射中不存在的键调用get(Object)方法时,将使用工厂创建对象。使用请求的键将创建的对象添加到地图。

这使您可以执行以下操作:

    Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
    map.get(notExistingKey).incrementAndGet();

调用get会为给定的键创建一个默认值。您可以使用的工厂参数指定如何创建默认值LazyMap.lazyMap(map, factory)。在上面的示例中,映射被初始化为AtomicInteger值为0 的新映射。


与可接受的答案相比,这具有一个优势,因为默认值是由工厂创建的。如果我的默认值是List<String>-使用接受的答案,我会为每个新密钥使用相同的列表,而不是(例如)new ArrayList<String>()从工厂使用一个列表。


0
/**
 * Extension of TreeMap to provide default value getter/creator.
 * 
 * NOTE: This class performs no null key or value checking.
 * 
 * @author N David Brown
 *
 * @param <K>   Key type
 * @param <V>   Value type
 */
public abstract class Hash<K, V> extends TreeMap<K, V> {

    private static final long serialVersionUID = 1905150272531272505L;

    /**
     * Same as {@link #get(Object)} but first stores result of
     * {@link #create(Object)} under given key if key doesn't exist.
     * 
     * @param k
     * @return
     */
    public V getOrCreate(final K k) {
        V v = get(k);
        if (v == null) {
            v = create(k);
            put(k, v);
        }
        return v;
    }

    /**
     * Same as {@link #get(Object)} but returns specified default value
     * if key doesn't exist. Note that default value isn't automatically
     * stored under the given key.
     * 
     * @param k
     * @param _default
     * @return
     */
    public V getDefault(final K k, final V _default) {
        V v = get(k);
        return v == null ? _default : v;
    }

    /**
     * Creates a default value for the specified key.
     * 
     * @param k
     * @return
     */
    abstract protected V create(final K k);
}

用法示例:

protected class HashList extends Hash<String, ArrayList<String>> {
    private static final long serialVersionUID = 6658900478219817746L;

    @Override
        public ArrayList<Short> create(Short key) {
            return new ArrayList<Short>();
        }
}

final HashList haystack = new HashList();
final String needle = "hide and";
haystack.getOrCreate(needle).add("seek")
System.out.println(haystack.get(needle).get(0));

0

我需要读取从服务器以JSON格式返回的结果,我无法保证该字段会存在。我正在使用从HashMap派生的org.json.simple.JSONObject类。这是我使用的一些辅助功能:

public static String getString( final JSONObject response, 
                                final String key ) 
{ return getString( response, key, "" ); }  
public static String getString( final JSONObject response, 
                                final String key, final String defVal ) 
{ return response.containsKey( key ) ? (String)response.get( key ) : defVal; }

public static long getLong( final JSONObject response, 
                            final String key ) 
{ return getLong( response, key, 0 ); } 
public static long getLong( final JSONObject response, 
                            final String key, final long defVal ) 
{ return response.containsKey( key ) ? (long)response.get( key ) : defVal; }

public static float getFloat( final JSONObject response, 
                              final String key ) 
{ return getFloat( response, key, 0.0f ); } 
public static float getFloat( final JSONObject response, 
                              final String key, final float defVal ) 
{ return response.containsKey( key ) ? (float)response.get( key ) : defVal; }

public static List<JSONObject> getList( final JSONObject response, 
                                        final String key ) 
{ return getList( response, key, new ArrayList<JSONObject>() ); }   
public static List<JSONObject> getList( final JSONObject response, 
                                        final String key, final List<JSONObject> defVal ) { 
    try { return response.containsKey( key ) ? (List<JSONObject>) response.get( key ) : defVal; }
    catch( ClassCastException e ) { return defVal; }
}   

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.