FindBugs warning: Inefficient use of keySet iterator - java

This a a similar question to [FindBugs warning: Inefficient use of keySet iterator instead of entrySet iterator
However, there I am trying to do something a little different. My current code is here:
for (Double key2 : sortedPolygons.keySet()) {
if (sortedPolygons.get(key2).getExteriorRing().equals(hole)) {
sortedPolygons.remove(key2);
break;
}
}
Doing something like the solution in the link does not work. Here is an implementation of said solution:
for(Map.Entry<Double, Polygon> entry : sortedPolygons.entrySet()) {
if (entry.getValue().getExteriorRing().equals(hole)) {
.....
The problem here is that I am trying to delete the entry. There is no entry.remove(). How can I replace my first block of code, without the FindBugs error:
Inefficient use of keySet iterator instead of entrySet iterator ->
This method accesses the value of a Map entry, using a key that was
retrieved from a keySet iterator. It is more efficient to use an
iterator on the entrySet of the map, to avoid the Map.get(key) lookup.
To note, the underlying structure is TreeMap, and it cannot be changed.

I fail to understand your reasoning: in the first snippet, you use
sortedPolygons.remove(key2);
to remove a key. Nothing prevents you to do the same in the second snippet:
sortedPolygons.remove(entry.getKey());
Whatever the way you iterate, this will lead to a ConcurrentModificationException anyway, because for most collections, you can't modify it while iterating on it, except by using its iterator.
Quote from the javadoc:
The iterators returned by the iterator method of the collections 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.
So the code should be:
for (Iterator<Map.Entry<Double, Polygon>> it = sortedPolygons.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<Double, Polygon> entry = it.next();
if (entry.getValue().getExteriorRing().equals(hole)) {
it.remove();
// if you want to exit the loop as soon as you found a match:
break;
}
}

How about you use the entrySet() iterator as suggested.
for(Iterator<Map.Entry<Double, Ploygon>> iter = sortedPolygons.entrySet().iterator();
iter.hasNext();) {
Map.Entry<Double, Ploygon> entry = iter.next();
if (condition)
iter.remove();
}
However you don't need the key so you can just iterate the values
for(Iterator<Ploygon> iter = sortedPolygons.values().iterator();
iter.hasNext();) {
Ploygon ploygon = iter.next();
if (condition)
iter.remove();
}

Related

Iterator inside Iterator ConcurrentModificationException

I am having the following problem:
Given an ArrayList (let's call it list), how can I "double-iterate" through it without getting ConcurrentModificationException?
Here's what I've tried:
iterator out = list.iterator();
iterator in;
while(out.hasNext()){
...
in = list.iterator();
while(in.hasNext()){
...
if(something)
in.remove();
}
You can't do that. A potential solution might be to mark objects to be removed, for example:
final List<Foo> toRemove = new ArrayList<>();
for (Foo a : list)
{
for (Foo b : list)
{
if (something)
{
toRemove.add(b);
}
}
}
list.removeAll(toRemove);
You may need some additional checks to see that the object isn't already marked for removal. It's impossible to say given how vague your example is.
You are trying to modify an iterator. It will give you concurrentModification exception.
In java 8 you can easily remove it using
list.removeIf(someCondition)
Try this link java8 collections
The Iterator instance provided through a call to List#iterator method preserves a count scalar allowing to detect external changes to the Collection container.
When an element is removed from the collection by any other mean than going through the same Iterator#remove(T) call, the count is not updated behind the scenes.
Therefore when you request for the #next() element through the iterator instance, the count is checked against an expected value and if both values does not match (since an element has been removed through another iterator) a ConcurrentModificationException is thrown (even though you may be working in a single threaded environment).
The solution whould be, as #Michael stated, to keep track of the container elements that should be removed then perform a bulk delete:
Collection<Object> temp = new ArrayList<>();
iterator out = list.iterator();
iterator in;
while (out.hasNext()) {
// ...
in = list.iterator();
while (in.hasNext()) {
// ...
if(something)
// just mark the element for deletion
temp.add(in.next());
}
}
// delete all the obsolete elements
list.removeAll(temp);
In collection once iterator creator If you try to modify the content not through same iterator it will throw concurrent exception.If you required some special kind of iterator then you can go ahead and implement your own.

Java HashMap add new entry while iterating

In a HashMap
map = new HashMap<String,String>();
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
it.remove(); //safely remove a entry
entry.setValue("new value"); //safely update current value
//how to put new entry set inside this map
//map.put(s1,s2); it throws a concurrent access exception
}
When i trying to add a new entry to map it throws ConcurrentModificationException. For remove and update iterator has safely removing methods. How to add new entry while iterating?
You need to consider what it means to put a value to a Map whilst iterating. HashMap defines no order over which its entries will be iterated over. So when you put a new entry, should the entry be returned by the iterator later or not. Consistency of behaviour is important. However, whichever way you decide you'll get inconsistent behaviour when you put a new value to a preexisting key. If the key has already been iterated over then the change won't appear and will appear if the key has yet to be produced by the iterator.
A simple way to overcome this problem is to create a temporary Map of the new key-value pairs and add the temporary Map to the main Map at the end of your iteration.
Map<String,String> values = ...
Map<String,String> temp = new HashMap<>();
for (Entry<String,String> entry : values.entrySet()) {
if ("some value".equals(entry.getValue()) {
temp.put(entry.getValue(), "another value");
}
}
values.putAll(temp);
You need to use ConcurrentHashMap to add elements while iterating the collection. HashMap uses fail-fast iterator, which throws ConcurrentModificationException when the collection is updated while iterating. Whereas ConcurrentHashMap uses fail-safe iterator, which basically works on the clone of the underlying collection and hence allows modification while iterating.
How about:
map = new HashMap<String,String>();
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
entry.setValue("new value"); // update current value
}
I checked the HashMap implementation, it does not change its modification count when updating an entry like this. I also see no reason why this shouldn't be allowed. Nothing is removed, nothing is added and no keys are changed.
What I did was to create an array with the current elements and then iterate through the array:
Feature[] initialFeatures = featureMap.values().toArray(new Feature[featureMap.values().size()]);
for (Feature feature : initialFeatures)
{/* Code that can safely add to the featureMap */}

Java delete arraylist iterator

I've an ArrayList in Java of my class 'Bomb'.
This class has a method 'isExploded', this method will return true if the bomb has been exploded, else false.
Now I'm trying to iterate through this arraylist, call this method isExploded and remove the element from the list if it returns true.
I know how to iterate:
for (Iterator i = bombGrid.listIterator(); i.hasNext();) {
if () {
i.remove();
}
But I've no idea how to access the method isExploded of the Bomb class itself via the iterator. Does anyone know the answer to this?
Sincerely,
Luxo
You'll need to get the Bomb using next :
for (Iterator i = bombGrid.listIterator(); i.hasNext();) {
Bomb bomb = (Bomb) i.next();
if (bomb.isExploded()) i.remove();
}
Or if you can get an Iterator<Bomb> from your bombGrid (is it an ArrayList<Bomb> ?):
Iterator<Bomb> i = bombGrid.listIterator();
while (i.hasNext()) {
Bomb bomb = i.next();
if (bomb.isExploded()) i.remove();
}
This supposes your iterator supports remove, which is the case for example by the one given by an ArrayList.
If you use Java 5, use generics:
List<Bomb> bombGrid = ...;
for (Iterator<Bomb> i = bombGrid.iterator(); i.hasNext();) {
if (i.next().isExploded()) {
i.remove();
}
}
No, you cannot remove inside an Iterator for ArrayList while iterating on it. Here's Javadoc extract :
The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, 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.
http://docs.oracle.com/javase/6/docs/api/java/util/ArrayList.html

I am getting java.util.ConcurrentModificationException thrown while using HashMap

How do i remove the key value pair in the code below comparing with elements in HashMap?
Map<BigDecimal, TransactionLogDTO> transactionLogMap = new HashMap<BigDecimal, TransactionLogDTO>();
for (BigDecimal regionID : regionIdList) {// Generation new logDTO
// objects for each in scope
// region
transactionLogMap.put(regionID, new TransactionLogDTO());
}
Set<BigDecimal> inScopeActiveRegionIdSet = new HashSet<BigDecimal>();
for (PersonDTO personDTO4 : activePersons) {
inScopeActiveRegionIdSet.add(personDTO4.getRegion());
}
for (BigDecimal bigDecimal : transactionLogMap.keySet()) {
if (!inScopeActiveRegionIdSet.contains(bigDecimal)) {
transactionLogMap.remove(bigDecimal);
}
}
As per javadoc
ConcurrentModificationException may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible
transactionLogMap.remove(bigDecimal);
Instead of for loop use Iterator and call remove on iterator.
Example:
Iterator iter = transactionLogMap.keySet().iterator();
while(iter.hasNext())
{
iter.remove();
}
OR
You may consider using ConcurrentHashMap
Note: Typed in code, use as reference. There may be syntax errors.
The problem is in these lines
for (BigDecimal bigDecimal : transactionLogMap.keySet()) {
if(!inScopeActiveRegionIdSet.contains(bigDecimal)) {
transactionLogMap.remove(bigDecimal);
}
}
You are iterating through the transactionLogMap whilst also directly modifying the underlying Collection when you call transactionLogMap.remove, which is not allowed because the enhanced for loop cannot see those changes.
The correct solution is to use the Iterator:
Iterator<BigDecimal> it = transactionLogMap.keySet().iterator();//changed for syntax correctness
while (it.hasNext()) {
BigDecimal bigDecimal = it.next();
if(!inScopeActiveRegionIdSet.contains(bigDecimal)) {
it.remove();
}
}
You cannot remove items from a collection while you are iterating over it. This causes the exception you're getting.
When you call:
for(TypeX x: collectionOfX){ ... }
What happens under the hood is that you're creating an iterator for collectionOfX, and iterating until you explicitly break from the cycle or hasNext() for the iterator is false.
If you need to remove items from the collection during the iteration, you need to replace that foreach construct with an explicit call to the Iterator. Something like:
Iterator<BigDecimal> iter = transactionLogMap.keySet().iterator();
while(iter.hasNext()) {
BigDecimal bDecimal = iter.next();
...
iter.remove(); //this will remove the current item from the collection, without raising an exception.
}
Alternatively, if inScopeActiveRegionIdSet is smaller in size, it might be shorter and faster to iterate it instead:
for (BigDecimal bigDecimal : inScopeActiveRegionIdSet) {
transactionLogMap.remove(bigDecimal);
}

Should we avoid hasNext() call in an Iterator?

Is it advisable not to use iterator.hasNext() in looping over an iterator?
For example I would like to set value obj to each element of a list. I could use the following code or make it more readable by using hasNext() in a loop.
int size = list.size();
ListIterator<? super T> itr = list.listIterator();
for (int i=0; i<size; i++) {
itr.next();
itr.set(obj);
}
Instead of these lines I could write my code like the following.
for (ListIterator<? super T> itr = list.listIterator(); itr.hasNext(); ) {
itr.next();
itr.set(obj);
}
Is it advisable not to use iterator.hasNext() in looping over an iterator?
Um, no. hasNext is the standard way you iterate with an iterator. That's what the enhanced-for statement does behind the scenes for an iterable, for example.
Having said that, your code is already ListIterator-specific, as you're using ListIterator.set - so your second block of code won't actually compile at the moment. Even if it did, it wouldn't work, as you still need to call next(). This would work though:
for (ListIterator<? super T> itr = list.listIterator(); itr.hasNext(); ) {
itr.next();
itr.set(obj);
}
Well, when NetBeans refactor you for-each loop to use of iterators, they do it in following way.
for-each:
for (T object : list) {
}
iterator pattern:
for (Iterator<T> it = list.iterator(); it.hasNext();) {
T object = it.next();
}
I think it is totally okay to use hasNext() on iterator while iterating.
There is no such recommendation not to use hasNext.
The Iterator API list has just three methods, add, remove and hasNext
Also from clean code the second approach looks far better then the first one.
When using an iterator, you should always call the hasNext() method. Otherwise, you may run into a NoSuchElementException when calling the next() method.
Of course, you should use hasNext(), but only for iterating over a collection, not for populating the collection. To fill the collection, work on the collection itself, not on it's iterator and to read from the collection use the for loop as described by #JMelnik.
Fill the collection
Collection<MyObject> list = ...;
while (something) {
list.add(myObject);
}
Read the collection
for (MyObject myObject : list) {
...
}

Categories