java.lang.UnsupportedOperationException when combining two Sets - java

I have 2 different instances of HashMap
I want to merge the keysets of both HashMaps;
Code:
Set<String> mySet = hashMap1.keySet();
mySet.addAll(hashMap2.keySet());
Exception:
java.lang.UnsupportedOperationException
at java.util.AbstractCollection.add(AbstractCollection.java:238)
at java.util.AbstractCollection.addAll(AbstractCollection.java:322)
I don't get a compile warning or error.
From java doc this should work. Even if the added collection is also a set:
boolean addAll(Collection c)
Adds all of the elements in the specified collection to this set if
they're not already present (optional operation). If the specified
collection is also a set, the addAll operation effectively modifies
this set so that its value is the union of the two sets. The behavior
of this operation is undefined if the specified collection is modified
while the operation is in progress.

If you look at the docs of the HashMap#keySet() method, you'll get your answer(emphasis mine).
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.
Therefore, you need to create a new set and add all the elements to it, instead of adding the elements to the Set returned by the keySet().

The result of keySet() does not support adding elements to it.
If you are not trying to modify hashMap1 but just want a set containing the union of the two maps' keys, try:
Set<String> mySet = new HashSet<String>();
mySet.addAll(hashMap1.keySet());
mySet.addAll(hashMap2.keySet());

Doesn't support by nature of Set which is from map.keySet(). It supports only remove, removeAll, retainAll, and clear operations.
Please read documentation

All the above answers are correct. If you still wants to know the exact implementation detail (jdk 8)
hashMap1.keySet() returns a KeySet<E> and
KeySet<E> extends AbstractSet<E>
AbstractSet<E> extends AbstractCollection<E>
In AbstractCollection,
public boolean add(E e) {
throw new UnsupportedOperationException();
}
addAll() calls add() and thats why you are getting an UOException

Just create your own Set with the keys of the Map like this:
Set set = new HashSet(map.keySet());
Then you can add whatever you want to it.

Related

Is there any data structure that has no duplicates but can have elements added to it while being iterated over?

I know a set has no duplicates but the issue is that I can't add elements to it while iterating over it using an iterator or for each loop. Is there any other way? Thank you.
The ConcurrentHashMap class can be used for this. For example:
Set<T> set = Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
(You can replace <T, Boolean> with <> and let the compiler infer the types. I wrote it as above for illustrative purposes.)
The Collections::newSetFromMap javadoc says:
Returns a set backed by the specified map. The resulting set displays the same ordering, concurrency, and performance characteristics as the backing map. In essence, this factory method provides a Set implementation corresponding to any Map implementation.
Since ConcurrentHashMap allows simultaneous iteration and updates, so does the Set produced as above. The catch is that an iteration may not see the effect of additions or removals made while iterating.
The concurrency properties of iteration can be inferred from the javadoc for ConcurrentHashMap.
Is there any other way.
It depends on your requirements, but there are potentially ways to avoid the problem. For example, you could:
copy the set before iterating it, OR
add the new element to another new set and add the existing elements to the new set to the new set after ... or while ... iterating.
However, these these are unlikely to work without a concurrency bottleneck (e.g. 1.) or a differences in behavior (e.g. 2.)
Not sure whether below approach fixes your problem but you can try it:
HashSet<Integer> original = new HashSet<>();
HashSet<Integer> elementsToAdd = new HashSet<>();
elementsToAdd.add(element); //while iterating original
original.addAll(elementsToAdd); //Once done with iterating, just add all.

What happens when an item is deleted from a list initialized by hashmap.values()?

I have the following declaration in class A:
Map<String, MyClass> myMap = Collections.synchronizedMap(new LinkedHashMap<String, MyClass>());
Class A also has this function:
public List<MyClass> getMapValuesAsList() {
return new ArrayList<>(myMap.values());
}
In class B, I have a List initialized like this:
List<MyClass> myList = cart.getMapValuesAsList();
What I assume is that myList keeps references to the values of myMap. When I call a setter function in an item in myList, the related value is also updated in myMap, supporting my assumption. However, whenever I delete an item from myList, myMap continues to keep the MyClass instance in it. This removed instance is probably not garbage collected since myMap still has a reference to it. Am I right?
Is there any automated way to delete key-value pair from map when the value is removed somewhere else?
UPDATE: Class B pass myList to an Android adapter. Therefore, I need some kind of get functionality. Collection doesn't have such a method. Any recommendations?
Your List contains references to the instances that are the values of your Map, so mutating the state of individual elements of the List mutates values of the Map.
On the other hand, your List is not backed by the Map, so removing elements to it won't affect the entries of the Map.
Is there any automated way to delete key-value pair from map when the value is removed somewhere else?
Yes, if you remove an element directly from the Collection returned by myMap.values(), it will also remove the corresponding key-value pair from the Map.
This is states in the Javadoc of values():
Collection java.util.Map.values()
Returns a Collection view of the values contained in this map.The collection is backed by the map, so changes to the map arereflected in the collection, and vice-versa. If the map ismodified while an iteration over the collection is in progress(except through the iterator's own remove operation),the results of the iteration are undefined. The collection supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Collection.remove, removeAll, retainAll and clear operations. It does notsupport the add or addAll operations.

Why Are Fixed-Sized Array-Backed Collections Allowed to not Implement all Interface Methods?

I'm trying to do the following:
Set<String> strings = map1.keySet();
strings.addAll(map2.ketSet());
At run-time, I'm getting an UnsupportedOperationException, and it seems that this is happening because strings is a fixed-sized array-backed set. But if strings is a set, why it it allowed to not implement addAll?
Because it is clearly mentioned in the javadoc of keySet (emphasis mine)
[...] 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.
Also, as mentioned in this comment not all collections have to implement this method.
More specifically - it (addAll of Collection) says,
UnsupportedOperationException - if the addAll operation is not supported by this collection
First the solution:
Set<String> strings = new HashSet<>(map1.keySet());
strings.addAll(map2.ketSet());
The problem that not the full interface API is supported by such backing class is simply due to the fragmentary nature of a very tight fitting implementation. For instance writing back to the original collection.
So it is a matter of either efficiency or backing (write-back) of the original collection, or both.
The wish for a more full gamma of operations can be easily done, as above.
The set created from the keySet method does not support add or addAll because the set is mapped to the original map, and you can't add a key to a map without also adding a value.

Why we get an empty map when clear values of it in Java?

I have a map.
Map<UUID, List<UUID>> parentsMap
When I trying to clear values:
parentsMap.values().clear()
It clears my map completely. I can do only like this:
parentsMap.forEach((k, v) -> v.clear())
I know that when we editing the .keySet() of the map, we changing the origin map.
But why when we editing the values() it have influence on the keys??
That's what the Javadoc says:
java.util.Map.values()
Returns a Collection view of the values contained in this map. The collection is backed by the map, so changes to the map are reflected in the collection, and vice-versa. If the map is modified while an iteration over the collection is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. The collection supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Collection.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Just like editing keySet() modified the backing Map, so does editing values().
Since you want to apply a clear operation on each individual List<UUID> value of the Map, you must iterate over all the values and call clear() on each of them separately, as you did in your parentsMap.forEach((k, v) -> v.clear()) call. You can also do parentsMap.values().forEach(List::clear).
You can also do map.replaceAll((k, v) -> null); :)
But anyway if you look at values() javadoc
Collection values()
Returns a Collection view of the values
contained in this map. The collection is backed by the map, so changes
to the map are reflected in the collection, and vice-versa. If the map
is modified while an iteration over the collection is in progress
(except through the iterator's own remove operation), the results of
the iteration are undefined. The collection supports element removal,
which removes the corresponding mapping from the map, via the
Iterator.remove, Collection.remove, removeAll, retainAll and clear
operations. It does not support the add or addAll operations.
You can use parentsMap.values().forEach(List::clear)
instead of
parentsMap.forEach((k, v) -> v.clear())
Hashmap is always modified when there is a key->value changed

Copying sets Java

Is there a way to copy a TreeSet? That is, is it possible to go
Set <Item> itemList;
Set <Item> tempList;
tempList = itemList;
or do you have to physically iterate through the sets and copy them one by one?
Another way to do this is to use the copy constructor:
Collection<E> oldSet = ...
TreeSet<E> newSet = new TreeSet<E>(oldSet);
Or create an empty set and add the elements:
Collection<E> oldSet = ...
TreeSet<E> newSet = new TreeSet<E>();
newSet.addAll(oldSet);
Unlike clone these allow you to use a different set class, a different comparator, or even populate from some other (non-set) collection type.
Note that the result of copying a Set is a new Set containing references to the objects that are elements if the original Set. The element objects themselves are not copied or cloned. This conforms with the way that the Java Collection APIs are designed to work: they don't copy the element objects.
Starting from Java 10:
Set<E> oldSet = Set.of();
Set<E> newSet = Set.copyOf(oldSet);
Set.copyOf() returns an unmodifiable Set containing the elements of the given Collection.
The given Collection must not be null, and it must not contain any null elements.
With Java 8 you can use stream and collect to copy the items:
Set<Item> newSet = oldSet.stream().collect(Collectors.toSet());
Or you can collect to an ImmutableSet (if you know that the set should not change):
Set<Item> newSet = oldSet.stream().collect(ImmutableSet.toImmutableSet());
Java 8+:
Set<String> copy = new HashSet<>(mySet);
The copy constructor given by #Stephen C is the way to go when you have a Set you created (or when you know where it comes from).
When it comes from a Map.entrySet(), it will depend on the Map implementation you're using:
findbugs says
The entrySet() method is allowed to return a view of the underlying
Map in which a single Entry object is reused and returned during the
iteration. As of Java 1.6, both IdentityHashMap and EnumMap did so.
When iterating through such a Map, the Entry value is only valid until
you advance to the next iteration. If, for example, you try to pass
such an entrySet to an addAll method, things will go badly wrong.
As addAll() is called by the copy constructor, you might find yourself with a Set of only one Entry: the last one.
Not all Map implementations do that though, so if you know your implementation is safe in that regard, the copy constructor definitely is the way to go. Otherwise, you'd have to create new Entry objects yourself:
Set<K,V> copy = new HashSet<K,V>(map.size());
for (Entry<K,V> e : map.entrySet())
copy.add(new java.util.AbstractMap.SimpleEntry<K,V>(e));
Edit: Unlike tests I performed on Java 7 and Java 6u45 (thanks to Stephen C), the findbugs comment does not seem appropriate anymore. It might have been the case on earlier versions of Java 6 (before u45) but I don't have any to test.

Categories

Resources