【问题标题】:Is there a Java HashMap implementation where key-values can't be changed after initial insertion?是否存在在初始插入后无法更改键值的 Java HashMap 实现?
【发布时间】:2021-10-07 17:01:32
【问题描述】:

我正在通过this 的问题将HashTable<K,V> 创建为final。我读了this 的答案,我们使用Collections.unmodifiableMap 在哈希表上放置一个不可修改的包装器。这让我想知道是否有 HashMap 实现或任何其他类似的结构,我可以随时插入,但是一旦我插入了一个键和值,就不允许在同一个键上再次插入。

例如:

private final HashMap<Integer, Integer> test = new HashMap<>();
test.put(1, 100);
test.put(2, 200);
/*
* Other insertions
*/
test.put(1, 50); //This should not be allowed

我能想到的一个解决方案是:

if(test.get(1) == null) {
    test.put(1, 50); //Similarly for all insertions
}

或创建一个将执行此检查的方法,例如:

private void putIfNotPresent(int key, int value) {
    if(test.get(key) == null) { //Considering that null will be returned if there is no mapping available for the key
        test.put(key, value);
    } else {
        throw new UnsupportedOperationException;
    }
}

我也知道 putIfAbsent(K, V) 方法,但如果有人尝试 put() 为已经存在的密钥设置某个值,这也不会禁止。

如果密钥已经存在,我提出的所有解决方案都不会阻止某人插入 HashMap,除非他们对每次插入都使用 putIfNotPresent() 自定义方法或 putIfAbsent 方法。是否有这样的HashMap 实现可用,一旦插入键和值,就不允许在同一个键上插入,即一旦插入带有值的键,它的行为就好像键的值是final

【问题讨论】:

  • (AFAIK) 不,没有。但是您可以使用这些属性编写自定义 Map 类。
  • 只是一个问题:为什么要抛出 UnsupportedOperationException 而不是 IllegalStateException?抛出IllegalStateException 是合乎逻辑的,因为当映射尝试引入重复项时,使用这些参数的操作处于非法状态。
  • @OlivierGrégoire 我刚刚将该异常添加为占位符,以表明它不应该发生。抛出IllegalStateException 更合乎逻辑。感谢您的意见!
  • @Hulk 是不是和默认的HashMapputIfAbsent 方法不一样,只是动作是原子执行的?
  • @MathewThomas 啊是的,你是对的,它也在Map 界面上。我不知何故记错了它是特定于 CHM 的。我想我错过了使用 java 8 将它添加到 Map 中(这是通过默认方法实现的)。 CHM 从 1.5 开始就有。

标签: java hashmap


【解决方案1】:

您可以像这样覆盖.put()

HashMap<Integer, Integer> test =
    new HashMap<Integer, Integer>() {
      @Override
      public Integer put(Integer key, Integer value) {
        // handle do not insert logic here
        if (this.containsKey(key)) {
          throw new UnsupportedOperationException();
        }
        
        // key not in map, insert
        return super.put(key, value);
      }
    };

【讨论】:

    【解决方案2】:

    默认情况下没有这样的实现。但是,您可以使用 Guava 的 ForwardingMap 轻松制作:

    class FailOnDupeMap<K,V> extends ForwardingMap<K,V> {
      private final Map<K,V> delegate = new HashMap<>();
      protected Map<K, V> delegate() { return delegate; } 
      @Override public V put(K key, V value) {
        var result = delegate().putIfAbsent(key, value);
        checkState(result == null, "a value already exist for key %s", key);
        return null;
      }
      @Override public void putAll(Map<? extends K,​ ? extends V> map) {
        standardPutAll(map);
      }
    }
    

    我写了一个putAll 的默认实现,但是基于状态抛出异常很大程度上取决于你,你可能想改变它以满足你的需要:要么根本不插入,要么插入直到找到欺骗。

    另外,请注意,当发现一个骗子时,我选择抛出一个IllegalStateException,但该异常没有Map 接口中声明。 Map 接口中声明的异常不应基于状态抛出。所以你最好把它记录下来,并说这个实现在这方面破坏了 Map 接口。

    【讨论】:

    • 使用 Guava 的ForwardingMap 实现这个有什么额外的好处吗?如果您在下面看到 Harshal 的 answer,它不使用外部库。
    • @MathewThomas 在我看来,由于多种原因,这个解决方案比 Harshal Parekh 的答案要干净得多。首先,ForwardingMap 是为了扩展,而HashMap 不是(它可以扩展,但不是意味着要扩展)。其次,它使用封装而不是扩展。如果你想明天使用TreeMap 或者明天传递一个特定的实例,它很容易适应。使用 Harshal 的解决方案,既困难又复杂。第三,类是命名的,因此是可重用的。使用 Harshal 的解决方案,每次使用时都必须重写。
    猜你喜欢
    • 2011-09-30
    • 2011-08-01
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多