Hashmap 的底层键集是如何实现的,以至于 add 方法失​​败?

How is the underlying keyset of a Hashmap implemented so that add method fails?

public class KeySetImmutable {
    public static void main(String[] args) {
        Map<String, String> hashMap = new HashMap<>();
        
        hashMap.put("Key1", "String1");
        hashMap.put("Key2", "String2");
        hashMap.put("Key3", "String3");
        hashMap.put("Key4", "String4");
        
        Set<String> keySet = hashMap.keySet();
        
        keySet.add("Key4");
        
        System.out.println(hashMap.keySet());
        System.out.println(keySet);
    }
}

在上面的代码中 keySet.add("Key4") 抛出 java.lang.UnsupportedOperationException。这是否意味着这个特定的 Set 实例是一个防止添加键的特殊实现?底层实现如何实现这一点?

keySet.remove("Key3"); 工作正常并且也从 HashMap 中删除了元素。

keySet() returns 覆盖 remove() 但继承 AbstractSetadd() 的特定 Set 实现,继承 [=16] =] 的 add(),抛出 UnsupportedOperationException.

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

final class KeySet extends AbstractSet<K> {
    ...
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
    ...
}

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
   ...
}

public abstract class AbstractCollection<E> implements Collection<E> {
    ...
    /**
     * {@inheritDoc}
     *
     * @implSpec
     * This implementation always throws an
     * {@code UnsupportedOperationException}.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     * @throws IllegalArgumentException      {@inheritDoc}
     * @throws IllegalStateException         {@inheritDoc}
     */
    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
    ...
}

请注意,这些只是特定 JDK 版本的实施细节。

重要的是 keySet() 的 Javadoc 指出:

Returns a Set view of the keys contained in this map.The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.