I came across the following code :
for(Map.Entry<Integer,VmAllocation> entry : allMap.entrySet()) {
// ...
}
What does Map.Entry<K,V> mean ? What is the entry object ?
I read that the method entrySet returns a set view of the map. But I do not understand this initialization in for-each loop.
Map.Entry is a key/value pair that forms one element of a Map. See the docs for more details.
You typically would use this with:
Map<A, B> map = . . .;
for (Map.Entry<A, B> entry : map.entrySet()) {
A key = entry.getKey();
B value = entry.getValue();
}
If you need to process each key/value pair, this is more efficient than iterating over the key set and calling get(key) to get each value.
Go to the docs: Map.Entry
Map.Entry is an object that represents one entry in a map. (A standard map has 1 value for every 1 key.) So, this code will iterator over all key-value pairs.
You might print them out:
for(Map.Entry<Integer,VmAllocation> entry : allMap.entrySet()) {
System.out.print("Key: " + entry.getKey());
System.out.println(" / Value: " + entry.getValue());
}
An entry is a key/value pair. In this case, it is a mapping of Integers to VmAllocation objects.
As the javadoc says
A map entry (key-value pair). The Map.entrySet method returns a collection-view of the map, whose elements are of this class. The only way to obtain a reference to a map entry is from the iterator of this collection-view. These Map.Entry objects are valid only for the duration of the iteration; more formally, the behavior of a map entry is undefined if the backing map has been modified after the entry was returned by the iterator, except through the setValue operation on the map entry.
You can learn about Map.Entry Docs
A map entry (key-value pair). The Map.entrySet method returns a collection-view of the map, whose elements are of this class. The only way to obtain a reference to a map entry is from the iterator of this collection-view. These Map.Entry objects are valid only for the duration of the iteration; more formally, the behavior of a map entry is undefined if the backing map has been modified after the entry was returned by the iterator, except through the setValue operation on the map entry.
Check For Each Loop Docs
for(Map.Entry<Integer,VmAllocation> entry : allMap.entrySet())
entry is a variable of type Map.Entry which is instantiated with the Entry type data in allMap with each iteration.
Related
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();
}
}
Map<String,String> mapp=new HashMap<>();
mapp.put("1", "abc");
mapp.put("1", "def");
System.out.println(mapp.size());
System.out.println(mapp.get("1"));
System.out.println(mapp);
o/p
1
def
{1=def}
How to get 'abc' value and wheather its present in the map as size is 1.
Can this be a case of hash collision, as the key is same can it produce same hashcode and store in the same index?
mapp.put("1", "def"); would return the previous value - "abc" - of the key "1".
You can see it with:
System.out.println(mapp.put("1", "def"));
Afterwards, there's no way to obtain the original value of the "1" key, since it has been overwritten.
This is specified in the Javadoc of put:
V java.util.Map.put(K key, V value)
Associates the specified value with the specified key in this map (optional operation). If the map previously contained a mapping for the key, the old value is replaced by the specified value. (A map m is said to contain a mapping for a key k if and only if m.containsKey(k) would return true.)
Returns:
the previous value associated with key, or null if there was no mapping for key.
Once you have clobbered a key's value in a hashmap, that value is for all intents and purposes gone. If you have a need to keep track of multiple values associated with a key, then consider using a map of some sort of collection:
Use a map of lists:
Map<String, List<String>> mapp = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("abc");
list.add("def");
mapp.put("1", list);
I have a Hashmap with duplicate keys, want to assign the elements in a Treemap format. I'm trying out this below code but this is not inserting duplicate keys in the Treemap.
HashMap<Integer,Integer> totalCustomersByPin = new HashMap<Integer,Integer>();
TreeMap<Integer,Integer> totalDeliveriesToPin = new TreeMap<Integer,Integer();
Iterator<Entry<Integer, Integer>> iterator = totalCustomersByPin.entrySet().iterator();
while(iterator.hasNext()) {
Entry<Integer, Integer> pair = iterator.next();
totalDeliveriesToPin.put(pair.getValue(), pair.getKey());
}
System.out.println("Top pincodes:" + totalDeliveriesToPin);
Java native maps don't support this. You can use MultiValuedMap from apache commons.
However I assume you want to sort it by numbers of values for the key. You'll need to implement Comparator for this, sorting by the number of values in the collection. TreeMap will not help here.
TreeMap#public V put(K key, V value) according to it's docs
Associates the specified value with the specified key in this map. If the map previously contained a mapping for the key, the old value is replaced.
To do what you are looking for you need a Multimap - which is essentially a Map to a List.
Map<Integer, List<String>> multiMap;
To add an object get the list for that key, if it is null add a list then add your value to the list, otherwise just add your value to the existing list.
I am a confused by the map interface. It has to use the entrySet() method for a collection view (or to use an iterator). An entrySet() returns a Set that contains the elements of its Map. Again, each of these Set elements is a Map.Entry object. How is that possible? As Set contains only one field, whereas Map.Entry is a key value pair? Can you explain briefly with example & flow .
A Set contains elements of some reference type. Map.Entry is a reference type, and can be used as the element of a Set.
Imagine that you have a data structure with key and value. One key for one value, one value for one key.
Map<K,V> is an interface for this data structure. It allows to get value by key.
Set<Map.Entry<K,V>> is an interface for the same data structure. It allows to get all pairs of key-value.
As per Orcale documentation : entrySet() of Map returns a Set view of the mappings contained in map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. The Map.entrySet method returns a collection-view of the map, whose elements are of this class. The only way to obtain a reference to a map entry is from the iterator of this collection-view. These Map.Entry objects are valid only for the duration of the iteration; more formally, the behavior of a map entry is undefined if the backing map has been modified after the entry was returned by the iterator.
Follow the below code snippet for further explanation :-
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapEntry {
public static void main(String... q){
Map<String,Integer> mapObj = new HashMap<String,Integer>();
mapObj.put("1", new Integer(1));
mapObj.put("2", new Integer(2));
mapObj.put("3", new Integer(3));
mapObj.put("4", new Integer(4));
// First Approach
Set outMap = mapObj.entrySet();
for(Map.Entry<String,Integer> tempMap : mapObj.entrySet()){
System.out.println("KEY : "+tempMap.getKey());
System.out.println("VALUE : "+tempMap.getValue());
}
// Second Approach
for(Iterator it = mapObj.entrySet().iterator(); it.hasNext();){
Map.Entry me = (Map.Entry)it.next();
System.out.println("2nd Approach - Key : "+me.getKey());
System.out.println("2nd Approach - Value : "+me.getValue());
}
}
}
Hope this helps.
I have a collection in the form of key, value pair and I was trying to sort it on the basis of
key itself
the values in the collection is
cd,20
ef,65
ab,28
gh,76
Now as shown the first is key , please advise me the ways by which I can sort it on the basis of key itself,
That is the output that I was looking would be.
ab,28
cd,20
ef,65
gh,76
Just create a TreeMap from the original Map. A TreeMap is sorted according to the natural ordering of the keys, and consequently
for (Map.Entry e : treeMap.entrySet()) {
// generics elided for clarity
will be sorted. From the doc for entrySet():
Returns a Set view of the mappings contained in this map. The set's
iterator returns the entries in ascending key order.
Using entrySet() means you can pull the keys and corresponding values in one operation, rather than have to get the keys, and go back to the map for the value. It's a small optimisation, but worth knowing.
TreeMap<String, Integer> map = new TreeMap<>();
// insert entries:
map.put("ab", 12);
// ...
// insertion order does not matter
for (String key : map.keySet()) {
System.out.println(key + ", " + map.get(key));
}
// will always print in sorted order