I’ve run into an oddity with Sets that reference a Maps keySet. It appears you can remove a value from that Set and it will remove the record from the corresponding Map. Yet if you try and add anything to the same set if results in an Exception:
System.FinalException: Collection is read-only
So calls to
Set.remove(x);
are allowed andSet.add(x);
throw exceptions.It makes sense that I shouldn’t be allowed to add anything to the Set as the Map wouldn’t then have a corresponding value.
But why allow the remove operation on an otherwise read only collection? It seems like an easy way to inadvertently change the Map when you are only dealing with a Set.
I encountered this when using a Set derived from the getPopulatedFieldsAsMap() Map. It seems like the safest option here is to clone the resulting Set before returning it to other code.
Anonymous Repo:
Map<Id, Account> accountsMap = new Map<Id, Account>([Select Id from Account limit 10]); Set<Id> accountIds = accountsMap.keySet(); Id testAccountId = accountsMap.values()[0].Id; System.assert(accountsMap.containsKey(testAccountId)); System.assert(accountIds.contains(testAccountId)); accountIds.remove(testAccountId); System.assert(!accountIds.contains(testAccountId)); // Fails as removal from the Set also removes from the Map //System.assert(accountsMap.containsKey(testAccountId)); // Throws System.FinalException: Collection is read-only accountIds.add(testAccountId);
Answer
One explanation may be that the underlying Java HashMap code behaves that way; this HashMap source code includes this code:
884 public Set<K> More ...keySet() {
885 Set<K> ks = keySet;
886 return (ks != null ? ks : (keySet = new KeySet()));
887 }
888
889 private final class KeySet extends AbstractSet<K> {
890 public Iterator<K> iterator() {
891 return newKeyIterator();
892 }
893 public int size() {
894 return size;
895 }
896 public boolean contains(Object o) {
897 return containsKey(o);
898 }
899 public boolean remove(Object o) {
900 return HashMap.this.removeEntryForKey(o) != null;
901 }
902 public void More clear() {
903 HashMap.this.clear();
904 }
905 }
and this documentation for the keySet
method:
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.
Seems like the designer felt that as removal via the set could be implemented it should be implemented: given that collections are mutable in general probably a reasonable decision.
Attribution
Source : Link , Question Author : Daniel Ballinger , Answer Author : Keith C