Collections : Maps

A Map is an object that maps keys to values. A map cannot contain duplicate keys. Each key can map to at most one value. The Map interface is not a subtype of the Collection interface. Therefore it behaves a bit different from the rest of the collection types. Since Map is an interface you need to instantiate a concrete implementation of the interface in order to use it. You can choose between HashMap, Hashtable, LinkedHashMap, TreeMap, Properties etc.

Lets see an example:


package mynotes.collections.maps;

import java.util.HashMap;
import java.util.Map;

public class MapBasics {

public static void main(String[] args) {
 Map<Integer, String> myMap = new HashMap<Integer, String>();
 myMap.put(1, "One");
 myMap.put(2, "Two");
 myMap.put(3, "Three");

String value = (String) myMap.get(2);
 System.out.println(value);

}

}

The output will be ‘Two’. We can do it without using Generics buts it always recommended to use it for type safety.

You can iterate either the keys or the values of a Map.


// key iterator
 Iterator<Integer> keyIterator = myMap.keySet().iterator();
 System.out.println("Keys:");
 while (keyIterator.hasNext()) {
 Integer keys = (Integer) keyIterator.next();
 System.out.println(keys);

}
 // value iterator
 Iterator<String> valueIterator = myMap.values().iterator();
 System.out.println("Values:");
 while (valueIterator.hasNext()) {
 String values = (String) valueIterator.next();
 System.out.println(values);

}

Output:


Keys:
1
2
3
Values:
One
Two
Three

Another way to loop around all the values:


for(Integer key : myMap.keySet()) {
 Object obj = myMap.get(key);
 System.out.println(obj);
 }

In order to remove an item from a Map you can use remove(Object key) method. However as discussed in List, removing should only be done using an Iterator. For example if you try removing an item like this:


//removing item the wrong way
 for(Integer key : myMap.keySet()) {
 if(key==2){
 myMap.remove(key);
 }
 }

Output:


Exception in thread "main" java.util.ConcurrentModificationException

The correct wat to remove an item:


Iterator<Integer> interatorToRemove=myMap.keySet().iterator();Iterator<Integer> interatorToRemove=myMap.keySet().iterator();
 while (interatorToRemove.hasNext()) {
Integer integer = (Integer) interatorToRemove.next();
 if(integer==2){ interatorToRemove.remove();
System.out.println("Removed");
}
}

Using object as Map keys

Consider the following example. Suppose we have an Employee class with only getters and setters. So


package mynotes.collections.maps;package mynotes.collections.maps;
public class Employee {
 private int id; private String name;
 public Employee() { }
 public Employee(int id, String name) { super(); this.id = id; this.name = name; }
 public int getId() { return id; }
 public void setId(int id) { this.id = id; }
 public String getName() { return name; }
 public void setName(String name) { this.name = name; }
}

Now lets add object of above to a map

Map map = new HashMap();
Employee emp1 = new Employee(1, "a");
Employee emp2 = new Employee(2, "b");
Employee emp3 = new Employee(3, "c");
Employee emp33 = new Employee(3, "c");

 map.put(emp1, emp1);
 map.put(emp2, emp2);
 map.put(emp3, emp3);
 map.put(emp33, emp33);

 System.out.println("Map Size =>" + map.size());

The above will return a size of 4, because there is no way for map to check for equality.

Now lets have a employee class with hascode and equality methods overriden based on id.


package mynotes.collections.maps;

public class Employee2 {
 private int id; private String name;
 public Employee2() { }
 public Employee2(int id, String name) { super(); this.id = id; this.name = name; }
 public int getId() { return id; }
 public void setId(int id) { this.id = id; }
 public String getName() { return name; }
 public void setName(String name) { this.name = name; }

 @Override
 public int hashCode() {
final int prime = 31; int result = 1; result = prime * result + id; return result; }

@Override
public boolean equals(Object obj) {
 if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Employee2 other = (Employee2) obj; if (id != other.id) return false; return true; }
 @Override
public String toString() { return "Employee2 [id=" + id + ", name=" + name + "]"; }
}

Using this is map now


Map map = new HashMap();
 Employee2 emp1 = new Employee2(1, "a");
Employee2 emp2 = new Employee2(2, "b");
Employee2 emp3 = new Employee2(3, "c");
Employee2 emp33 = new Employee2(3, "c");
 map.put(emp1, emp1);
 map.put(emp2, emp2);
map.put(emp3, emp3);
map.put(emp33, emp33);
 System.out.println("Map 2 Size =>" + map.size());

Above code will return 3, as we are trying to add duplicate and not our map knows how to check equality.

Now what will happen if we try to change the id of an object after its inserted a map.


Map map = new HashMap();
Employee2 emp1 = new Employee2(1, "a");
Employee2 emp2 = new Employee2(2, "b");
Employee2 emp3 = new Employee2(3, "c");
map.put(emp1, emp1);
map.put(emp2, emp2);
map.put(emp3, emp3);
System.out.println("Map 2 Size =>" + map.size());
map.keySet().stream().forEach((key)->{
//check if key and value points to same thing
if(key==map.get(key)) {
System.out.println("key==value"+key + map.get(key));
}else {
System.out.println("key!=value"+key + map.get(key));
} });

map.get(emp3).setId(4);

System.out.println("==============Changed object" + map.size());

map.keySet().stream().forEach((key)->{
//check if key and value points to same thing
if(key==map.get(key)) {
System.out.println("key==value"+key + map.get(key));
}else {
System.out.println("key!=value"+key + map.get(key));
} });
boolean exsists = map.containsKey(new Employee2(3, null));
System.out.println("exsists 3 =>" + exsists);
exsists = map.containsKey(new Employee2(4, "c"));
System.out.println("exsists 4 =>" + exsists);

Above result will be


Map 2 Size =>3
key==valueEmployee2 [id=1, name=a]Employee2 [id=1, name=a]
key==valueEmployee2 [id=2, name=b]Employee2 [id=2, name=b]
key==valueEmployee2 [id=3, name=c]Employee2 [id=3, name=c]
==============Changed object3
key==valueEmployee2 [id=1, name=a]Employee2 [id=1, name=a]
key==valueEmployee2 [id=2, name=b]Employee2 [id=2, name=b]
key!=valueEmployee2 [id=4, name=c]null
exsists 3 =>false
exsists 4 =>false

If key’s hash code changes after the key-value pair (Entry) is stored in HashMap, the map will not be able to retrieve the Entry.  Key’s hashcode can change if the key object is mutable. Mutable keys in HahsMap can result in data loss.

Always use immutable keys in hashmap.

Difference between HashMap and HashTable 

HashMap Hashtable (legacy)
HashMap lets you have null values as well as one null key. HashTable does not allows null values as key and value.
The  iterator in the HashMap is fail-safe (If you change the map while iterating, you’ll know). The enumerator for the Hashtable is not fail-safe.
HashMap is unsynchronized. Hashtable is synchronized.

If you have multiple thread accessing you HashMap then Collections.synchronizedMap(Map<K,V> m) can be used. Synchronization at Object level. Every read/write operation needs to acquire lock. Locking the entire collection is a performance overhead. This essentially gives access to only one thread to the entire map & blocks all the other threads. It may cause contention. SynchronizedHashMap returns Iterator, which fails-fast on concurrent modification.

If the hashcode is null (something went wrong inside), and the object is inserted into a Map, it will throw NullpointerException.

ConcurrentHashMap – You should use ConcurrentHashMap when you need very high concurrency in your project. It is thread safe without synchronizing the whole map.
Reads can happen very fast while write is done with a lock. There is no locking at the object level. The locking is at a much finer granularity at a hashmap bucket level.
ConcurrentHashMap doesn’t throw a ConcurrentModificationException if one thread tries to modify it while another is iterating over it. ConcurrentHashMap uses multitude of locks.

To make you map unmodifiable, you can call Collection method: Collections.unmodifiableMap(myMap);

Now if you try to put/remove things into your map you will get java.lang.UnSupportedOperation Exception

The LinkedHashMap implementation is a subclass of the HashMap class. Elements of a HashMap are unordered. The elements of a LinkedHashMap are ordered. By default, the entries of a LinkedHashMap are in key insertion order, that is, the order in which the keys are inserted in the map. This order does not change if a key is re-inserted, because no new entry is created if the key’s entry already exists. A LinkedHashMap can also maintain its elements in (element) access order, that is, the order in which its entries are accessed, from least-recently accessed to most-recently accessed entries. This ordering mode can be specified in one of the constructors of the LinkedHashMap class. Adding, removing, and finding entries in a LinkedHashMap can be slightly slower than in a HashMap, as an ordered doubly-linked list has to be maintained.

TreeMap also maps a key and a value. In a TreeMap the data will be sorted in ascending order of keys according to the natural order for the key’s class, or by the comparator provided at creation time.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: