How Set having Map.entrySet() values - java

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.

Related

Can I use keySet to modify HashMap during iteration?

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();
}
}

when add key-value pair to a hashMap, why Java makes hashMap's hashCode change?

If you look at java's hashCode method inside hashMap, you will find:
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
So when you insert things into hashMap, hashMap's hashCode will change. Thus, if we insert an empty hashMap into hashSet, then insert something to this hashMap, then call hashSet.contains(hashMap), it will return false.
Why does Java allow such behavior? This will easily cause duplicate items in a hashSet.
Try run the following code:
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
public class Main {
public static void main(String[] args) {
HashSet<HashMap<Integer, String>> set = new HashSet<>();
HashMap<Integer, String> b = new HashMap<>();
System.out.println("adding hashcode: " + b.hashCode() + "to set");
set.add(b);
b.put(8, "arsenal");
for(HashMap<Integer, String> map: set){
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
System.out.println(pair.getKey() + " = " + pair.getValue());
}
}
System.out.println("Finding b: " + set.contains(b));
System.out.println(b.hashCode());
set.add(b);
for(HashMap<Integer, String> map: set){
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
System.out.println(pair.getKey() + " = " + pair.getValue());
}
}
}
}
Why Java allow such behavior?
Because keys are supposed to be invariant, according to their equals() implementation. If you mutate a key in such a way that comparisons by means of its equals() method are affected, then the behaviour of the map is unspecified.
And this is exactly what you're doing when you change your HashMap while it's an element of the HashSet, since HashSet is actually backed by a HashMap.
This is an excerpt of the Map interface documentation:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.
If I were you, I wouldn't use a mutable map as the key of a structure that relies on the immutability of its elements.
The Pigeonhole principle gaurantees there may be collisions. There are other Map implementations, including LinkedHashMap and TreeMap which do not exhibit the behavior you describe. In the case of LinkedHashMap it is described in the Javadoc (in part) as a hash table and linked list implementation of the Map interface, with predictable iteration order.

What is Map.Entry<K,V> interface?

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.

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

Map.Entry: How to use it?

I'm working on creating a calculator.
I put my buttons in a HashMap collection and when I want to add them to my class, which extends JPanel, I don't know how can I get the buttons from my collection.
So I found on the internet the 2 last lines of my code, but I don't know their meaning.
Here is my code:
import java.awt.Component;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JPanel;
public class PanneauCalcul extends JPanel {
private HashMap<String, JButton> listbouton = new HashMap<String, JButton>() ;
public PanneauCalcul() {
for(int i = 0; i < 10; i ++) {
listbouton.put("num" + i, new JButton("" + i)) ;
}
listbouton.put("add", new JButton("+")) ;
listbouton.put("soustract", new JButton("-")) ;
listbouton.put("multiply", new JButton("x")) ;
listbouton.put("divise", new JButton("/")) ;
listbouton.put("equal", new JButton("=")) ;
Set entrys = listbouton.entrySet() ;
Iterator iter = entrys.iterator() ;
while(iter.hasNext()) {
Map.Entry me = (Map.Entry)iter.next(); //don't understand
this.add((Component) me.getValue()) ; //don't understand
}
EcouteCalcul ecout = new EcouteCalcul(this) ;
}
}
I don't understand how can we use Map.Entry--which is an interface--without redefining Map.Entry's functions.
Map.Entry is a key and its value combined into one class. This allows you to iterate over Map.entrySet() instead of having to iterate over Map.keySet(), then getting the value for each key. A better way to write what you have is:
for (Map.Entry<String, JButton> entry : listbouton.entrySet())
{
String key = entry.getKey();
JButton value = entry.getValue();
this.add(value);
}
If this wasn't clear let me know and I'll amend my answer.
Note that you can also create your own structures using a Map.Entry as the main type, using its basic implementation AbstractMap.SimpleEntry. For instance, if you wanted to have an ordered list of entries, you could write:
List<Map.Entry<String, Integer>> entries = new ArrayList<>();
entries.add(new AbstractMap.SimpleEntry<String, Integer>(myStringValue, myIntValue));
And so on. From there, you have a list of tuples. Very useful when you want ordered tuples and a basic Map is a no-go.
This code is better rewritten as:
for( Map.Entry me : entrys.entrySet() )
{
this.add( (Component) me.getValue() );
}
and it is equivalent to:
for( Component comp : entrys.getValues() )
{
this.add( comp );
}
When you enumerate the entries of a map, the iteration yields a series of objects which implement the Map.Entry interface. Each one of these objects contains a key and a value.
It is supposed to be slightly more efficient to enumerate the entries of a map than to enumerate its values, but this factoid presumes that your Map is a HashMap, and also presumes knowledge of the inner workings (implementation details) of the HashMap class. What can be said with a bit more certainty is that no matter how your map is implemented, (whether it is a HashMap or something else,) if you need both the key and the value of the map, then enumerating the entries is going to be more efficient than enumerating the keys and then for each key invoking the map again in order to look up the corresponding value.
A Map consists of key/value pairs. For example, in your code, one key is "Add" and the associated value is JButton("+"). A Map.Entry is a single key/value pair contained in the Map. It's two most-used methods are getKey() and getValue(). Your code gets all the pairs into a Set:
Set entrys = listbouton.entrySet() ;
and iterates over them. Now, it only looks at the value part using me.getValue() and adds them to your PanneauCalcul
this.add((Component) me.getValue()) ; //don't understand
Often this type of loop (over the Map.Entry) makes sense if you need to look at both the key and the value. However, in your case, you aren't using the keys, so a far simpler version would be to just get all the values in your map and add them. e.g.
for (JButton jb:listbouton.values()) {
this.add(jb);
}
One final comment. The order of iteration in a HashMap is pretty random. So the buttons will be added to your PanneauCalcul in a semi-random order. If you want to preserve the order of the buttons, you should use a LinkedHashMap.
A Map is a collection of Key + Value pairs, which is visualized like this:
{[fooKey=fooValue],barKey=barValue],[quxKey=quxValue]}
The Map interface allows a few options for accessing this collection: The Key set [fooKey, barKey,quxKey], the Value set [fooValue, barValue, quxValue] and finally entry Set [fooKey=fooValue],barKey=barValue],[quxKey=quxValue].
Entry set is simply a convenience to iterate over the key value pairs in the map, the Map.Entry is the representation of each key value pair. An equivalent way to do your last loop would be:
for (String buttonKey: listbouton.keySet()) {
this.add(listbouton.get(buttonKey)) ;
}
or
for (JButton button: listbouton.values()) {
this.add(button) ;
}
Hash-Map stores the (key,value) pair as the Map.Entry Type.As you know that Hash-Map uses Linked Hash-Map(In case Collision occurs). Therefore each Node in the Bucket of Hash-Map is of Type Map.Entry. So whenever you iterate through the Hash-Map you will get Nodes of Type Map.Entry.
Now in your example when you are iterating through the Hash-Map, you will get Map.Entry Type(Which is Interface), To get the Key and Value from this Map.Entry Node Object, interface provided methods like getValue(), getKey() etc. So as per the code, In your Object you are adding all operators JButtons viz (+,-,/,*,=).
Map.Entry interface helps us iterating a Map class
Check this simple example:
public class MapDemo {
public static void main(String[] args) {
Map<Integer,String> map=new HashMap();
map.put(1, "Kamran");
map.put(2, "Ali");
map.put(3, "From");
map.put(4, "Dir");
map.put(5, "Lower");
for(Map.Entry m:map.entrySet()){
System.out.println(m.getKey()+" "+m.getValue());
}
}
}
public HashMap<Integer,Obj> ListeObj= new HashMap<>();
public void addObj(String param1, String param2, String param3){
Obj newObj = new Obj(param1, param2, param3);
this.ListObj.put(newObj.getId(), newObj);
}
public ArrayList<Integer> searchdObj (int idObj){
ArrayList<Integer> returnList = new ArrayList<>();
for (java.util.Map.Entry<Integer, Obj> e : this.ListObj.entrySet()){
if(e.getValue().getName().equals(idObj)) {
returnList.add(e.getKey());
}
}
return returnList;
}

Categories