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.
Related
I was working on analyzing fail-safe iterators with Maps and verifying, if we are updating the keyset, will the operation happen on the clone or the actual map
private static void failSafeIterator() {
ConcurrentHashMap<String, String> map=new ConcurrentHashMap<>();
map.put("a", "one");
map.put("b", "two");
Iterator<String> keyIterator=map.keySet().iterator();
while(keyIterator.hasNext()){
String key=keyIterator.next();
System.out.println(key+":"+map.get(key));
map.put("c", "three");
map.put("q", "four");
map.put("W", "five");
}
System.out.println(map.get("q"));
}
As per the above code snippet,
the addition of c,q and w should have happened on the clone and not on
the actual collection
But i can see the update happening over the collection.
Also the output is a bit confusing as not all the key value pairs are printed, even though the key is present in the map.
Output:
a:one
b:two
c:three
W:five
four
The problem here is that you don't understand what weakly consistent iterator stands for, when using keySet().iterator(), specifically this part of the documentation:
they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction.
Imagine a case like this: you are iterating a ConcurrentHashMap and printing whatever it has. Once you have seen a certain bucket and showed all of it's elements, you move to the next one, also updating the (suppose you add a key-value pair to it) previous one. Updates to that previous one are not going to be shown, although they do exist.
After your loop, you can do :
System.out.println(map);
And see that everything is now present.
I have a hashmap that takes String and HashSet as key and values.
I am trying to update the map and add values in it.
I cannot understand which of the following methods to use-
map.putIfAbsent(str.substring(i,j),new HashSet<String>).add(str);
//this method gives nullpointerexception
map.computeIfPresent(str.substring(i,j),(k,v)->v).add(str);
In the output I can see the same key being added twice with an initial value and updated value.
Someone please tell me how to use these methods.
The preferable way to do it is with Map#computeIfAbsent. This way a new HashSet is not created unnecessarily, and it will return the value afterwards.
map.computeIfAbsent(str.substring(i, j), k -> new HashSet<>()).add(str);
There is no reason to choose between putIfAbsent and computeIfPresent. Most notably, computeIfPresent in entirely inappropriate as it, as its name suggests, only computes a new value, when there is already an old one, and (k,v)->v even makes this computation a no-op.
There are several options
containsKey, put and get. This is the most popular pre-Java 8 one, though its the most inefficient of this list, as it incorporates up to three hash lookups for the same key
String key=str.substring(i, j);
if(!map.containsKey(key))
map.put(key, new HashSet<>());
map.get(key).add(str);
get and put. Better than the first one, though it still may incorporate two lookups. For ordinary Maps, this was the best choice before Java 8:
String key=str.substring(i, j);
Set<String> set=map.get(key);
if(set==null)
map.put(key, set=new HashSet<>());
set.add(str);
putIfAbsent. Before Java 8, this option was only available to ConcurrentMaps.
String key=str.substring(i, j);
Set<String> set=new HashSet<>(), old=map.putIfAbsent(key, set);
(old!=null? old: set).add(str);
This only bears one hash lookup, but needs the unconditional creation of a new HashSet, even if we don’t need it. Here, it might be worth to perform a get first to defer the creation, especially when using a ConcurrentMap, as the get can be performed lock-free and may make the subsequent more expensive putIfAbsent unnecessary.
On the other hand, it must be emphasized, that this construct is not thread-safe, as the manipulation of the value Set is not guarded by anything.
computeIfAbsent. This Java 8 method allows the most concise and most efficient operation:
map.computeIfAbsent(str.substring(i, j), k -> new HashSet<>()).add(str);
This will only evaluate the function, if there is no old value, and unlike putIfAbsent, this method returns the new value, if there was no old value, in other words, it returns the right Set in either case, so we can directly add to it. Still, the add operation is performed outside the Map operation, so there’s no thread safety, even if the Map is thread safe. But for ordinary Maps, i.e. if thread safety is not a concern, this is the most efficient variant.
compute. This Java 8 method will always evaluate the function and can be used in two ways. The first one
map.compute(str.substring(i, j), (k,v) -> v==null? new HashSet<>(): v).add(str);
is just a more verbose variant of computeIfAbsent. The second
map.compute(str.substring(i, j), (k,v) -> {
if(v==null) v=new HashSet<>();
v.add(str);
return v;
});
will perform the Set update under the Map’s thread safety policy, so in case of ConcurrentHashMap, this will be a thread safe update, so using compute instead of computeIfAbsent has a valid use case when thread safety is a concern.
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.
Title say it all:
There is a slight ambiguity in the documentation as I understand it.
First the documentation speaks about insertion-ordered LinkedHashMap's and notes that the iteration order is not affected upon inserting an item already mapped.
Then, it introduces access-ordered LinkedHashMap's and insists on the fact that "merely a get is a structural modification", i.e. it affects iteration order.
But, it is not clear whether the ''put()'' of an existing item is a structural modification.
I expect the answer to be yes.
This simple piece of code should answer your question:
final Map<String, String> x = new LinkedHashMap<>(10, 0.75f, true);
x.put("a", "a");
x.put("b", "b");
System.out.println(x);
x.put("a", "a");
System.out.println(x);
prints
{a=a, b=b}
{b=b, a=a}
Interpretation: put under an existing key, even with the same value, is a structural modification.
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.