11

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 and Set.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); 
7

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.

5

It's not a read-only map, per se (the error is shared with other truly read-only collections, like Trigger.new). It allows you to remove values, which also removes them from the map. You can use the removeAll, retainAll, remove, and clear methods to remove some or all of the mapped value pairs. I actually wish they allowed add and addAll methods, because it would be an efficient way to prepopulate a map with null values.

As a useful example, if you wanted to retain only values that match in two different maps, you can:

map1.keySet().retainAll(map2.keySet());

This is clearly much more efficient than what you'd be able to do without this capability. While it's rare that you'll ever have a need for this, there are legitimate cases for being able to remove keys and their values by manipulating the Set directly.

And yes, if you intend to just get a copy of the keys that you can use later, you should definitely clone the Set before using it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.