Can I use keySet to modify HashMap during iteration? - java

I know that collections shouldn't be modified during iteration. So we should have workaround.
I have a code:
Map<Key, Value> map = getMap(); // map generating is hidden
for (Key key : map.keySet()) {
if (isToRemove(key)) {
map.remove(key);
} else {
map.put(key, getNewValue());
}
}
Is it undefined behavior or valid code?
keySet documentation sais that changes of the map are reflected in returned set and vice-versa. Does it mean that previous code is unacceptable?

The answer from davidxxx is correct (+1) in pointing out that the view collections on the Map are linked to the map, and that modifications to the map while iterating a view collection may result in ConcurrentModificationException. The view collections on a map are provided by the entrySet, keySet, and values methods.
Thus, the original code:
Map<Key, Value> map = getMap();
for (Key key : map.keySet()) {
if (isToRemove(key)) {
map.remove(key);
} else {
map.add(key, getNewValue());
}
}
will most likely throw ConcurrentModificationException because it modifies the map during each iteration.
It's possible to remove entries from the map while iterating a view collection, if that view collection's iterator supports the remove operation. The iterators for HashMap's view collections do support this. It is also possible to set the value of a particular map entry (key-value pair) by using the setValue method of a Map.Entry instance obtained while iterating a map's entrySet. Thus, it's possible to do what you want to do within a single iteration, without using a temporary map. Here's the code to do that:
Map<Key, Value> map = getMap();
for (var entryIterator = map.entrySet().iterator(); entryIterator.hasNext(); ) {
var entry = entryIterator.next();
if (isToRemove(entry.getKey())) {
entryIterator.remove();
} else {
entry.setValue(getNewValue());
}
}
Note the use of Java 10's var construct. If you're not on Java 10, you have to write out the type declarations explicitly:
Map<Key, Value> map = getMap();
for (Iterator<Map.Entry<Key, Value>> entryIterator = map.entrySet().iterator(); entryIterator.hasNext(); ) {
Map.Entry<Key, Value> entry = entryIterator.next();
if (isToRemove(entry.getKey())) {
entryIterator.remove();
} else {
entry.setValue(getNewValue());
}
}
Finally, given that this is a moderately complicated map operation, it might be fruitful to use a stream to do the work. Note that this creates a new map instead of modifying an existing map in-place.
import java.util.Map.Entry;
import static java.util.Map.entry; // requires Java 9
Map<Key, Value> result =
getMap().entrySet().stream()
.filter(e -> ! isToRemove(e.getKey()))
.map(e -> entry(e.getKey(), getNewValue()))
.collect(toMap(Entry::getKey, Entry::getValue));

The HashMap.keySet() method states more precisely:
The set is backed by the map, so changes to the map are reflected in
the set, and vice-versa.
It means that the elements returned by keySet() and the keys of the Map refer to the same objects. So of course changing the state of any element of the Set (such as key.setFoo(new Foo());) will be reflected in the Map keys and reversely.
You should be cautious and prevent the map from being modified during the keyset() iteration :
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
You can remove entries of the map as :
The set supports element removal, which removes the corresponding
mapping from the map, via the Iterator.remove, Set.remove, removeAll,
retainAll, and clear operations.
But you cannot add entries in :
It does not support the add or addAll operations.
So in conclusion, during keySet() iterator use Set.remove() or more simply iterate with the Iterator of the keySet and invoke Iterator.remove() to remove elements from the map.
You can add new elements in a temporary Map that you will use after the iteration to populate the original Map.
For example :
Map<Key, Value> map = getMap(); // map generating is hidden
Map<Key, Value> tempMap = new HashMap<>();
for (Iterator<Key> keyIterator = map.keySet().iterator(); keyIterator.hasNext();) {
Key key = keyIterator.next();
if (isToRemove(key)) {
keyIterator.remove();
}
else {
tempMap.put(key, getNewValue());
}
}
map.putAll(tempMap);
Edit :
Note that as you want to modify existing entries of the map, you should use an Map.EntrySet as explained in the Stuart Marks answer.
In other cases, using an intermediary Map or a Stream that creates a new Map is required.

If you run your code you get a ConcurrentModificationException. Here is how you do it instead, using an iterator over the keys set or the equivalent Java8+ functional API:
Map<String, Object> bag = new LinkedHashMap<>();
bag.put("Foo", 1);
bag.put("Bar", "Hooray");
// Throws ConcurrentModificationException
for (Map.Entry<String, Object> e : bag.entrySet()) {
if (e.getKey().equals("Foo")) {
bag.remove(e.getKey());
}
}
// Since Java 8
bag.keySet().removeIf(key -> key.equals("Foo"));
// Until Java 7
Iterator<String> it = bag.keySet().iterator();
while (it.hasNext()) {
if (it.next().equals("Bar")) {
it.remove();
}
}

Related

How to remove elements from a HashMap without getting ConcurrentModificationException [duplicate]

This question already has answers here:
Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
(31 answers)
Closed 2 years ago.
I am comparing every entry in a HashMap to every other entry in the same HashMap. While iterating the HashMap I remove some elements based on some conditions. However I keep getting the ConcurrentModificationException.
Iterator<Entry<String, String>> i = map.entrySet().iterator();
while (i.hasNext()) {
Entry<String, String> next = i.next();
for (Entry<String,String> e : map.entrySet()) {
if (e.getKey() != next.getKey()){
String[] positions_e = fields_e[1].split("-");
int start_e = Integer.parseInt(positions_e[0]);
int end_e = Integer.parseInt(positions_e[1]);
String[] positions_next = fields_next[1].split("-");
int start_next = Integer.parseInt(positions_next[0]);
int end_next = Integer.parseInt(positions_next[1]);
if (start_e <= start_next || end_e <= end_next )) {
i.remove();
}
}
}
Use iterator or lambda with removeIf for the second loop.
for (Entry<String,String> e : map.entrySet())
Iterator for map: https://www.techiedelight.com/iterate-map-in-java-using-entryset/
Lambda for map: https://javarevisited.blogspot.com/2017/08/how-to-remove-key-value-pairs-from-hashmap-java8-example.html#axzz5eHbDQxwp
HereĀ“s a method that does not involve a second map or list and also increases the readability of your code:
Extract your condition in a spearate method:
private boolean myCondition(Entry<String, String> currentEntry, Map<String, String> map) {
for (Entry<String, String> entry : map.entrySet()) {
...
if (...) {
return true;
}
return false;
}
}
Use java8 streams to filter the map according to your condition:
Map<String, String> filteredMap = map.entrySet().stream()
.filter(entry -> myCondition(entry, map))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
For the safety of your code, java does not allow you to remove elements that belong to your data structure while you are iterating it. A way of doing it so is: cloning your hashmap, iterating through the copy map and doing the comparison on it. if the condition shows that an element must be removed, try to remove it from your original hash map.
You're removing elements from the collection via the iterator i while iterating over it with a different iterator, the implied one used by your foreach loop.
You're always going to have this problem if you have nested iterations over the same collection and try to remove items from within the inner loop. Using either the inner or outer iterator to remove the element from the collection produces a ConcurrentModificationException from the other iterator.
Since you're using i to remove the element, the best strategy here is probably to break out of the inner loop after calling remove.

How/Can I write a for each loop with a Hashmap in java? [duplicate]

This question already has answers here:
How do I efficiently iterate over each entry in a Java Map?
(46 answers)
Closed 4 years ago.
I need to iterate each value of a HashMap in my method but it gives me a syntax error at the for each loop
Library.java:12: error: for-each not applicable to expression type
for(String book : library){
^ required: array or java.lang.Iterable found: HashMap
This is the relevant code
public void getFinishedBooks(HashMap<String, Boolean> library)
{
if(library.size()<1)
{
System.out.println("Library is empty!");
}
else
{
for(String book : library)
{
if(library.get(book) ==true)
{
System.out.println(book);
}
}
}
}
You can iterate over the set of entries:
for (Entry<String, Boolean> book : library.entrySet()) {
if (book.getValue()) {
System.out.println(book.getKey());
}
}
Map.entrySet() returns a Set of entries (java.util.Map.Entry). Each entry contains a pair with a key and its value.
You have different ways to iterate over a Map
forEach from Java 8 (this is more efficient)
library.forEach((k, v) -> System.out.println(v));
forEach and Entry
for (library.Entry<String, Boolean> pair : library.entrySet()) {
System.out.println(pair.getValue());
}
forEach and keySet
for (String key : library.keySet()) {
System.out.println(library.get(key));
}
You can use entrySet().
Quote from https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html:
public Set> entrySet()
Returns a Set view of the mappings 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, 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.
You can traverse using below code by java8:
Map<String, Boolean> library= new HashMap<>();
library.put("Book1",true);
library.put("Book2",true);
library.put("Book3",false);
library.forEach((key, value) -> {
if(value){
System.out.println("Book = " + value);
}
});

Separate chaining for HashTables in Java

Based on the following code snippet :
Hashtable balance = new Hashtable();
Enumeration names;
String str;
double bal;
balance.put("Zara", new Double(3434.34)); //first entry for Zara
balance.put("Mahnaz", new Double(123.22));
balance.put("Zara", new Double(1378.00)); //second entry for Zara
balance.put("Daisy", new Double(99.22));
balance.put("Qadir", new Double(-19.08));
System.out.println(balance.entrySet());
.
Output : [Qadir=-19.08, Mahnaz=123.22, Daisy=99.22, Zara=1378.0]
Why isn't chaining happening here? When I re-enter with Zara as key the old value is overwritten. I expected it to be added at the end of the Linked List at Zara".hashcode() index.
Does Java use separate chaining only for collision handling?
If I can't use chaining( as I'v tried above) please suggest a common method to do so.
Does Java use separate chaining only for collision handling?
Yes. You can only have one entry per key in a Hashtable (or HashMap, which is what you should probably be using - along with generics). It's a key/value map, not a key/multiple-values map. In the context of a hash table, the term "collision" is usually used for the situation where two unequal keys have the same hash code. They still need to be treated as different keys, so the implementation has to cope with that. That's not the situation you're in.
It sounds like you might want a multi-map, such as one of the ones in Guava. You can then ask a multimap for all values associated with a particular key.
EDIT: If you want to build your own sort of multimap, you'd have something like:
// Warning: completely untested
public final class Multimap<K, V> {
private final Map<K, List<V>> map = new HashMap<>();
public void add(K key, V value) {
List<V> list = map.get(key);
if (list == null) {
list = new ArrayList();
map.put(key, list);
}
list.add(value);
}
public Iterable<V> getValues(K key) {
List<V> list = map.get(key);
return list == null ? Collections.<V>emptyList()
: Collections.unmodifiableList(list);
}
}
Quote from the documentation of Map (which Hashtable is an implementation of):
An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.
(emphasis mine)
The documentation of put() also says:
If the map previously contained a mapping for the key, the old value is replaced by the specified value
So if you want multiple values associated with a key, use a Map<String, List<Double>> instead of a Map<String, Double>. Guava also has a Multimap, which does what you want without having to deal with Lists explicitely as with a Map<String, List<Double>>.

Accessing a Map via index?

Is it tossibe to aceess a Map<Integer, Integer> via index?
I need to get the second element of the map.
You're using the wrong data structure. If you need to lookup by key, you use a Map. If you need to lookup by index or insertion order, use something that lets you index, like an array or list or linked list.
If you need to lookup by both, then you need to create a composite data structure that tracks both keys and insertion order (implementation would be backed by a Map and one of the above aforementioned data structures).
There's even one built into the framework: LinkedHashMap.
There is no direct way to access a map "via index", but it looks like you want a LinkedHashMap, which provides a predictable iteration order:
... which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)
A definition of index is not applicable to Map, as it's not an ordered collection by default.
You can use a TreeMap, which implements NavigableMap, and then iterate the key set using the navigableKeySet() method.
If you just need to get the second element all the time. Why not use a iterator and then do next ,next.
It will depends of Map implementation, but if you want to retrieve the second inserted element, you can use a LinkedHashMap and then create an iterator on values.
Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>();
map.put(1, 1);
map.put(2, 2);
Integer value = null;
if (map.size() > 1) {
Iterator<Integer> iterator = map.values().iterator();
for (int i = 0; i < 2; i++) {
value = iterator.next();
}
}
// value contains second element
System.out.println(value);
Map does not store elements in the insertion order. It stores elements into buckets based on the value of the hashCode of the element that is being stored. So no, you cannot get it by index.
Anyways, you could imitate something like this by using the LinkedHashMap implementation of the Map interface, which remembers the insertion order (unlinke the HashMap).
You would have to "hack" with manual index counter and the code would look something like this:
Map<String, String> map= new LinkedHashMap<>();
map.put("1", "one");
map.put("2", "two");
map.put("3", "three");
int index= 0;
for (String key : map.keySet()) {
if (index++ == 1) {
System.out.println(map.get(key));
}
}
Will print:
"two"
Which is what you want.
You can also use org.apache.commons.collections.map.ListOrderedMap from apache commons-collection. It implements Map and provides some methods from the List interface, like get(int index) and remove(int index).
It uses an ArrayList internally, so performance will be better than iterating on a Map to retrieve a value at specified position.
Not sure if this is any "cleaner", but:
If use LinkedHashMap and u want to retrieve element inserted second following will work
List keys = new ArrayList(map.keySet());
Object obj = map.get(keys.get(1));
//do you staff here

How can I sort the keys of a Map in Java?

This is a very basic question, I'm just not that good with Java. I have a Map and I want to get a list or something of the keys in sorted order so I can iterate over them.
Use a TreeMap, which is an implementation of the SortedMap interface. It presents its keys in sorted order.
Map<String, Object> map = new TreeMap<String, Object>();
/* Add entries to the map in any order. */
...
/* Now, iterate over the map's contents, sorted by key. */
for (Map.Entry<String, ?> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
If you are working with another Map implementation that isn't sorted as you like, you can pass it to the constructor of TreeMap to create a new map with sorted keys.
void process(Map<String, Object> original) {
Map<String, Object> copy = new TreeMap<String, Object>(original);
/* Now use "copy", which will have keys in sorted order. */
...
}
A TreeMap works with any type of key that implements the Comparable interface, putting them in their "natural" order. For keys that aren't Comparable, or whose natural ordering isn't what you need, you can implement your own Comparator and specify that in the constructor.
You have several options. Listed in order of preference:
Use a SortedMap:
SortedMap<whatever> myNewMap = new TreeMap<whatever>(myOldMap);
This is vastly preferable if you want to iterate more than once. It keeps the keys sorted so you don't have to sort them before iterating.
There is no #2.
There is no #3, either.
SortedSet<whatever> keys = new TreeSet<whatever>(myMap.keySet());
List<whatever> keys = new ArrayList<whatever>(myMap.keySet());
Collections.sort(keys);
The last two will get you what you want, but should only be used if you only want to iterate once and then forget the whole thing.
You can create a sorted collection when iterating but it make more sense to have a sorted map in the first place. (As has already been suggested)
All the same, here is how you do it.
Map<String, Object> map;
for(String key: new TreeSet<String>(map.keySet()) {
// accessed in sorted order.
}
Apart from the methods mentioned in other answers, with Java 8 streams, another shorthand to get a sorted key list from a map would be -
List<T> sortedKeys = myMap.keySet().stream().sorted().collect(Collectors.toList());
One could actually get stuff done after .sorted() as well (like using a .map(...) or a .forEach(...)), instead of collecting it in the list and then iterating over the list.

Categories