In the java.util.TreeMap javadoc there is this statement:
All Map.Entry pairs returned by methods in this class and its views represent snapshots of mappings at the time they were produced. They do not support the Entry.setValue method. (Note however that it is possible to change mappings in the associated map using put.)
I don't get this line. In what way they do not support setValue method? When I use entrySet() and iterate over Map.Entry object it sets value fine.
Map<String, Integer> map = new TreeMap<>();
map.put("dbc", 1);
map.put("abc", 1);
map.put("cbc", 1);
for(Map.Entry<String, Integer> item: map.entrySet()) {
item.setValue(1);
}
This is a known issue.
There is an OpenJDK tracker entry (JDK-8038146) that notes that this is a javadoc error. But there is more to it than this.
There is also a Java Bug Database entry (bug id 7006877) that explains that the javadoc was changed to say that in Java 6, and that it is actually true for the alternative version of TreeMap that you get (got) if you run the JVM with aggressive optimizations enabled.
This ticket also says that the issue affected Java 7, and was fixed in Java 8. They apparently removed the alternative TreeMap implementation ... though they didn't change the javadoc.
Commentary:
If the issue trackers are to be believed (and I've understood them correctly), then the javadoc probably ought to say that the Entry.setValue method may not be supported in Java 6 and Java 7. But the misleading sentences could be entirely removed for Java 8 onwards.
Whether that is the correct thing to do is somewhat debatable, because some people need to understand how their new Java code might run on older platforms. Maybe it would be best to leave this as a historical footnote.
It appears the comment you quoted isn't entirely accurate.
The TreeMap class has a number of methods which return single Map.Entry objects: firstEntry, lastEntry, higherEntry, lowerEntry, etc...
I believe the comment is meant in reference to the methods on TreeMap which return a single Map.Entry. Those methods return a Map.Entry by making an immutable copy of the underlying Entry ( via AbstractMap.SimpleImmutableEntry).
These immutable copies will throw an UnsupportedOperationException if you call their setValue.
After reading the comment you quoted I would have thought that entrySet would return Immutable copies. The javadoc of the TreeMap.entrySet method states:
Returns a Set view of the mappings contained in this map. The set's iterator returns the entries in ascending key order. 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, or through the setValue operation on a map entry returned by the iterator) 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.
Given that changes to the Set returned by entrySet must operate on the underlying map I can see why TreeMap doesn't attempt to wrap the Entries - that would severely complicate efforts to keep the Set and the Map in sync.
Related
This question already has answers here:
java.lang.UnsupportedOperationException when combining two Sets
(5 answers)
Closed 6 years ago.
Why this is throwing UnsupportedException?
public static void main(String[] args) {
Map<String,String> map=new HashMap<String,String>();
map.put("a", "a value");
Set<String> set=map.keySet();
set.add("b");
}
The Map interface does not define which implementation of Set the keySet() should return. Specifically, HashMap#keySet() returns an instance of the inner class java.util.Map$KeySet. The returned Set represents the keys of the Map, so adding to it would, in essence, mean adding a key to the map. Ultimately, this is a design decision the JDK developers took, but IMHO, it's the right one. Assume this operation was allowed - What would this operation do? Add a key to the map pointing to a null value? What if you extend Map to perform some operation on the value when it's added? How would such a scenario be treated?
The answer is in the documentation:
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.
The solution would be creating a new set, and add the result to it. But not to the same set you returned from the keySet method.
The Addition of element is not supported in the result returned by keySet(). It does not support the add or addAll operations.It only supports remove, removeAll, retainAll, and clear operations.
for more details please read this
For a given type of Map, are there any guarantees that iterating over the Collection views returned by the keySet, values and entries methods are iterated in the same order?
Background: I'm wondering whether transforming
public static void doSomethingForEachEntry(Map<String, Integer> someMap) {
for (String key : someMap.keySet()) {
doSomething(someMap.get(key));
}
}
to
public static void doSomethingForEachEntry(Map<String, Integer> someMap) {
for (Integer value : someMap.values()) {
doSomething(value);
}
}
is guaranteed to keep iteration order unchanged.
While it is true that you can't rely on a specific ordering unless the Map implementation explicitly defines it, there is a sentence in the API documentation that implies there is a single shared ordering for the map and all its collection views:
The order of a map is defined as the order in which the iterators on the map's collection views return their elements.
(my emphasis)
For this to be satisfied, a map has an inherent order (even though it may not be specified, and may change as the map is modified), and all collection views must correspond to this order. Whether that constitutes a guarantee, and in particular whether all third-party map implementations will honour it, is another question.
It's also worth noting that these are explicitly defined in the Map interface as views that are backed by the map, (e.g. if you remove an element from the keySet, the corresponding Map entry must be removed from the map). This means in reality it's less likely that you'll get different orderings from a correct Map implementation than it would be if for example you made shallow copies of the collection views.
Having said all that, if the question is "is this a safe refactor?" then the answer is "yes, as long as the original code isn't itself broken". If the method relies on a specific ordering, and therefore a specific Map implementation, the method should be declared to accept only that type of Map. Otherwise, you have a potential timebomb if the underlying Map implementation changes down the line (and I have seen software break in real life because of this with a JDK update).
If a particular caller is relying on a specific ordering because it knows it's passing an ordered Map implementation, that's fine and that order will be preserved after your refactor.
Iteration order depends on the specific implementation of Map you use. Refer to the documentation if you know the Map type. If you don't then don't rely on any iteration order.
I need a container that contains [key, Value] pair.
Here, key = Integer, Value = User Defined class object.
Mutiple threads are trying to add [key, Value] pair in above container.
If key already present in the container, I want to update the value by checking some condition.
At the end I want container in sorted order, according to Key.
My efforts -
I used this synchronizedSortedMap and Sorted Map for above task.
SortedMap<Integer, USER_DEFINED_OBJECT> m = Collections.synchronizedSortedMap(new TreeMap<Integer, USER_DEFINED_OBJECT>());
This helps me to add pairs concurrently on above container.
And, yes If key already present, then I check some condition, then proceed.
Is my approach always thread safe ? If not, please correct me.
Updated
USER_DEFINED_OBJECT has some field index.
At the time of adding, I am checking if key is already present, then compare current USER_DEFINED_OBJECT with already present USER_DEFINED_OBJECT on the basis of above mentioned(in point 1) filed "index". If currect "index" is greater than update.
Use ConcurrentHashMap from java.util package, read the API ConcurrentHashMap
java.util.concurrent.ConcurrentSkipListMap
A scalable concurrent ConcurrentNavigableMap implementation. The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
This class implements a concurrent variant of SkipLists providing expected average log(n) time cost for the containsKey, get, put and remove operations and their variants. Insertion, removal, update, and access operations safely execute concurrently by multiple threads. Iterators are weakly consistent, returning elements reflecting the state of the map at some point at or since the creation of the iterator. They do not throw ConcurrentModificationException, and may proceed concurrently with other operations. Ascending key ordered views and their iterators are faster than descending ones.
The concurrent collections let you call methods like put, remove etc. in kind of transaction. Therefore it's thread safe.
From what I understood your scenario for adding new [key, value] pair is as follows:
Check whether the mapping already exists
If not, just add it
If yes, update the existing value in the mapping based on some check
I doubt there is an implementation in place which does this for you in thread-safe way. In the case I understood your use-case correctly you will need to add some manual synchronization on your own to make the update steps transactional.
The class docs state that Entrys cannot be modified via .setValue(...) but also caveat that put(...) works fine.
Does that mean put(...) will work fine when iterating over the collection views like navigableKeySet() (i.e., not result in a ConcurrentModificationException), as long as no structural modifications (i.e., adding a new key) are made?
I'm in the middle of testing it out, but if I'm unable to break the iteration, I'd still like some verification that it's working fine (instead of me being unable to break it).
The javadocs for TreeMap state:
A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with an existing key is not a structural modification.
Therefore one can assume that changing the values associated with a given key while iterating over the set of keys is allowed.
I believe you are right that as long as you're not making structural modification by adding a new key, you're in no danger of ConcurrentModificationException.
That is, code like this is legal by design:
NavigableMap<Integer,String> nmap =
new TreeMap<Integer,String>();
nmap.put(1, "One");
nmap.put(2, "Two");
nmap.put(3, "Three");
nmap.put(4, "Four");
nmap.put(5, "Five");
NavigableSet<Integer> nkeys =
nmap.navigableKeySet().subSet(2, true, 4, true);
for (Integer key : nkeys) {
nmap.put(key, "Blah");
}
System.out.println(nmap);
// prints "{1=One, 2=Blah, 3=Blah, 4=Blah, 5=Five}"
I'm backing this up also by looking at the source code of the OpenJDK version, where modCount++; is only executed if a new Entry is added.
(In TreeMap, modCount is declared as private transiet, but its analog in AbstractList is declared protected transient, and there it's intended usage is documented to count the number of structural modification for the purpose of detecting ConcurrentModificationException).
Additionally, the documentation for TreeMap explicitly clarifies what counts as structural modification:
A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with an existing key is not a structural modification
Based on all of the above, I will say that yes, a put that does not add a new key/value pair is not a structural modification, and thus would not cause a ConcurrentModificationException.
Say I am iterating over a Map in Java... I am unclear about what I can to that Map while in the process of iterating over it. I guess I am mostly confused by this warning in the Javadoc for the Iterator interface remove method:
[...] The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
I know for sure that I can invoke the remove method without any issues. But while iterating over the Map collection, can I:
Change the value associated with a key with the Map class put method (put with an existing key)?
Add a new entry with the Map class put method (put with a new key)?
Remove an entry with the Map class remove method?
My guess is that I can probably safely do #1 (put to an existing key) but not safely do #2 or #3.
Thanks in advance for any clarification on this.
You can use Iterator.remove(), and if using an entrySet iterator (of Map.Entry's) you can use Map.Entry.setValue(). Anything else and all bets are off - you should not change the map directly, and some maps will
not permit either or both of the aforementioned methods.
Specifically, your (1), (2) and (3) are not permitted.
You might get away with setting an existing key's value through the Map object, but the Set.iterator() documentation specifically precludes that and it will be implementation specific:
If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined. (emphasis added)
If you take a look at the HashMap class, you'll see a field called 'modCount'. This is how the map knows when it's been modified during iteration. Any method that increments modCount when you're iterating will cause it to throw a ConcurrentModificationException.
That said, you CAN put a value into a map if the key already exists, effectively updating the entry with the new value:
Map<String, Object> test = new HashMap<String, Object>();
test.put("test", 1);
for(String key : test.keySet())
{
test.put(key, 2); // this works!
}
System.out.println(test); // will print "test->2"
When you ask if you can perform these operations 'safely,' you don't have to worry too much because HashMap is designed to throw that ConcurrentModificationException as soon as it runs into a problem like this. These operations will fail fast; they won't leave the map in a bad state.
There is no global answer. The map interface let the choice to the users. Unfortunately, I think that all the implementations in the jdk use the fail-fast implementation (here is the definition of fail-fast, as it stated in the HashMap Javadoc):
The iterators returned by all of this
class's "collection view methods" are
fail-fast: if the map is structurally
modified at any time after the
iterator is created, in any way except
through the iterator's own remove
method, the iterator will throw a
ConcurrentModificationException. Thus,
in the face of concurrent
modification, the iterator fails
quickly and cleanly, rather than
risking arbitrary, non-deterministic
behavior at an undetermined time in
the future.
In general, if you want to change the Map while iterating over it, you should use one of the iterator's methods. I have not actually tested to see if #1 will will work, but the others definitely will not.