【问题标题】:HashMap to return default value for non-found keys?HashMap 为未找到的键返回默认值?
【发布时间】:2011-11-23 01:50:37
【问题描述】:

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

【问题讨论】:

  • 您可以检查密钥是否存在并返回默认值。或者扩展类并修改行为。甚至你可以使用 null - 并在你想使用它的任何地方进行检查。
  • 这与stackoverflow.com/questions/4833336/… 相关/重复,那里讨论了其他一些选项。
  • 查看 Map API 的 Java 8 解决方案 getOrDefault() link

标签: java dictionary hashmap


【解决方案1】:

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

【讨论】:

  • @Larry,当null 完全没问题时,仅仅为了这个功能而将HashMap 子类化对我来说似乎有点傻。
  • 不过,如果你使用NullObject 模式,或者不想在你的代码中分散空检查,那就不好了——我完全理解这种愿望。
【解决方案2】:

不是直接的,但是你可以扩展类来修改它的get方法。这是一个现成的示例:http://www.java2s.com/Code/Java/Collections-Data-Structure/ExtendedVersionofjavautilHashMapthatprovidesanextendedgetmethodaccpetingadefaultvalue.htm

【讨论】:

    【解决方案3】:

    [更新]

    正如其他答案和评论者所指出的,从 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;
      }
    }
    

    【讨论】:

    • 确切地说,您可能需要将条件从(v == null) 调整为(v == null &amp;&amp; !this.containsKey(k)),以防他们故意添加null 值。我知道,这只是一个极端案例,但作者可能会遇到它。
    • @maerics:我注意到你使用了!this.containsValue(null)。这与!this.containsKey(k) 略有不同。如果某个 other 键已显式分配值nullcontainsValue 解决方案将失败。例如:map = new HashMap(); map.put(k1, null); V v = map.get(k2); 在这种情况下,v 仍然是null,对吗?
    • 总的来说,我认为这是一个坏主意 - 我会将默认行为推送到客户端或不声称是 Map 的委托中。特别是,缺少有效的 keySet() 或 entrySet() 将导致任何期望遵守 Map 契约的问题。并且 containsKey() 所暗示的无限有效键集可能会导致难以诊断的不良性能。不过,并不是说它可能无法用于某些特定目的。
    • 这种方法的一个问题是如果值是一个复杂的对象。 Map#put 不会按预期工作。
    • 不适用于 ConcurrentHashMap。在那里,您应该检查 get 的结果是否为 null。
    【解决方案4】:

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

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

    如果您一开始不负责创建地图,也可以传入现有地图。

    【讨论】:

    • +1 尽管有时重新发明轮子比为一小部分简单功能引入大量依赖项更容易。
    • 有趣的是,我使用的许多项目在类路径中已经有类似的东西(Apache Commons 或 Google Guava)
    • @bartosz.r,绝对不是移动端
    【解决方案5】:

    您可以简单地创建一个继承 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 接口的contract(这可能会导致一些难以发现的错误)。

    【讨论】:

    • 你有一点不覆盖get 方法。另一方面 - 您的解决方案不允许通过接口使用类,这可能经常发生。
    【解决方案6】:
    /**
     * 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));
    

    【讨论】:

      【解决方案7】:

      在 Java 8 中,使用 Map.getOrDefault。它接受键,如果没有找到匹配的键则返回值。

      【讨论】:

      • getOrDefault 很好,但每次访问地图时都需要默认定义。在创建静态值映射时,定义一次默认值也将具有可读性优势。
      • 这对你自己来说是微不足道的。 private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
      • @JackSatriano 是的,但您必须硬编码默认值,或者为其设置一个静态变量。
      • 使用computeIfAbsent查看下面的答案,当默认值昂贵或每次都应该不同时更好。
      • 虽然它对内存来说更糟,并且只有在默认值构造/计算成本高昂的情况下才会节省计算时间。如果它很便宜,您可能会发现它的性能更差,因为它必须插入到地图中,而不仅仅是返回默认值。当然是另一种选择。
      【解决方案8】:

      您不能创建一个完全执行此操作的静态方法吗?

      private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
          return map.containsKey(key) ? map.get(key) : defaultValue;
      }
      

      【讨论】:

      • 静态存储在哪里?
      【解决方案9】:

      Java 8 为 Map 接口引入了一个不错的 computeIfAbsent 默认方法,该方法存储惰性计算值,因此不会破坏映射契约:

      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,是的,这不完全符合 OP 的要求,但一些谷歌搜索的人仍然发现这个侧面答案很有用。正如其他答案所提到的,如果不破坏地图合同,就不可能完全实现 OP 的要求。此处未提及的另一个解决方法是use another abstraction instead of Map
      • 在不违反地图合同的情况下,完全可以实现 OP 的要求。不需要变通方法,只使用 getOrDefault 是正确的(最新的)方式,computeIfAbsent 是错误的方式:您将通过存储结果来调用mappingFunction 和内存(对于每个缺失的键)。我看不出有什么好的理由这样做,而不是 getOrDefault。我所描述的是 Map 合约有两种不同方法的确切原因:有两个不同的用例不应混淆(我必须在工作中修复一些)。这个答案分散了混乱。
      【解决方案10】:

      我需要读取从服务器返回的 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; }
      }   
      

      【讨论】:

        【解决方案11】:

        用途:

        myHashMap.getOrDefault(key, defaultValue);
        

        【讨论】:

          【解决方案12】:

          我发现LazyMap 很有帮助。

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

          这允许你做这样的事情:

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

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

          【讨论】:

          • 这比公认的答案有一个优势,因为默认值是由工厂创建的。如果我的默认值是 List&lt;String&gt; - 使用公认的答案,我会冒险为每个新密钥使用 same 列表,而不是(比如说)来自工厂的 new ArrayList&lt;String&gt;()。跨度>
          【解决方案13】:

          在混合 Java/Kotlin 项目中,还可以考虑 Kotlin 的 Map.withDefault

          Accessing Kotlin extension functions from Java

          【讨论】:

            【解决方案14】:

            在 java 8+ 上

            Map.getOrDefault(Object key,V defaultValue)
            

            【讨论】:

              【解决方案15】:
                  public final Map<String, List<String>> stringMap = new ConcurrentHashMap<String, List<String>>() {
                      @Nullable
                      @Override
                      public List<String> get(@NonNull Object key) {
                          return computeIfAbsent((String) key, s -> new ArrayList<String>());
                      }
                  };
              

              HashMap会导致死循环,所以用ConcurrentHashMap代替HashMap,

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2013-02-25
                • 2019-04-12
                • 2019-04-05
                • 1970-01-01
                • 2011-09-28
                • 1970-01-01
                相关资源
                最近更新 更多