Saturday, April 13, 2019

Collection Frame Work - HashMap in Java


HashMap

HashMap extends AbstractMap and implements Map Interface. HashMap is used when we want to store unordered and unsorted  key value pairs where key is always unique.

HashMap is based on Hash Table data structure. HashMap  uses hash table and bucketing logic to store each entries (key and value).

When you want to retrieve an object out of HashMap, you have to pass the key. Based on the hashcode of the provided key, the underlying algorithms locates the key and returns the associated value.


HashMap, like any other hash based collections, provides constant in time operations like put, get, remove, contains etc. Because, the logic is based on hashcode and the size of the HashMap doesn’t affect the time it requires.

Feature of HashMap


  • HashMap is used to store object in key value pair where key is always unique.
  • key and value  both are object type.
  • HashMap support null key and null value, However one and only one null key allowed.
  • Value may be duplicated, for a key. HashMap update latest value for given key.
  • By default  entries in HashMap are unordered and unsorted while  Iterating.
  • Iterators on the HashMap are fail-fast and throws ConcurrentModificationException when the HashMap is modified, while an iterator is active.
  • Hashmap operations are not synchronized and you will have to synchronoize the threads accessing HashMap on you own.

  • Constructor of HashMap


    HashMap() - Default HashMap constructor, with default capacity 16 and default loadFactor of 0.75
    HashMap(Map<? extends K, ? extends V> map) - This Constructor is used to create Map based on some existing Map.
    HashMap(int capacity) - This Constructor is used initialize HashMap with provided capacity and default load factor
    HashMap(int capacity, double loadFactor) - this constructor is used to initialize HashMap with provided capacity and load factor.

    What is Capacity and Load Factor                                                 

    Like any other Collection class HashMap also comes with default Capacity and default load Factor. An instance of HashMap depends upon two factor 1. Capacity and 2 . Load Factor.
    The Capacity is number of bucket in hash table.  The HashMap has default capacity 16 and load factor 0.75. Which means when a HashMap is 75 % occupied the background process create a bigger HashMap based on some algorithm, and  migrate all entries of old HashMap to new HashMap. Old HashMap become the candidate of garbage collector.

    Example of HashMap

    HashMap using Constructor

    //Default Constructor default capacity and default load factor

    Map<String,String> map=new HashMap<>();
    map.put("country","India");
    map.put("capital","Delhi");

    This is most basic and most used form of HashMap creation. First you will create a HashMap using Default constructor, then using put(K k, V v) method add entries to Map. This is dyanmic and mutable map so you can put , replace or remove entries any number of times.

    Create Immutable HashMap using Collections utility Class

    Immutable Map once created can't be modified i.e. you cannot add, update, remove any entries. if you try you will get UnsupportedOperationException.

    Map<String,String> map=new HashMap<>();
    map.put("country","India");
    map.put("capital","Delhi");

    Map<String,String> immutableMap=Collections.unmodifiableMap(map);

    you can create Immutable Map using unmodifiableMap() method of Collections utility class, it is two step process.
     1. firstly you create a mutable map
    2. then convert this mutable map using Collections class unmodifiableMap() static method.
    This will create two physical Map which not good as production point of view.

    Create Immutable HashMap using Java 9 of() and ofEntries() method

    using Map.of() overloaded method, this method is overloaded upto 10 key value pair

    for example:-

    Map<String,String> immutableMap = Map.of("Country","India");
    Map<String,String>immutableMap=Map.of("country","india","state","delhi");

     Map.ofEntries() method  not restricted like Map.of() method, it have varargs type of Map.Entry<? extends K, ? extends V>. 
    Map.entry() method is used to create Map.Entry type object.

    Map<String,String> immutableMap=Map.ofEntries(Map.entry("country","India"),Map.entry("Capital","Delhi"));
                                                                             

    Create Singleton and Empty HashMap using Collections

    What is Singleton Hash Map


    Singleton Hash Maps have single entry and immutable in nature, In other words you cannot add , update or remove another entry
    Map<String,String> singletonMap=Collections.singletonMap("Country","India");


    What is Empty Hash Map


    Empty Hash Maps also immutable , you cannot add, update, remove it later. As name suggest you can create an empty Hash Map has no entry.

    Map<String,String> emptyHashMap=Collections.emptyMap();


     Using Stream to create HashMap


    Java 8 Streams have Collectors.toMap() method to  create Map from different things.

    Convert List to Map using Stream Collectors.toMap method

    Lets consider Book Class which have two member fields isbn and title and getters and setters and a constructor with both members.

    public class Book{
    String isbn;
    String title;
    //getter and setter
    //constructor using parameter
    }

    List of Books

    List<Book> bookList = new ArrayList<>();
    bookList.add(new Book("isbn1","Java Tech"));
    // 
    The Java Stream Collectors.toMap() is most popular method to create maps. This method take two Lambda expression, out of which first is key other is value.


    Map<String,String> bookMap=bookList.stream().collect(Collectors.toMap(Book::getIsbn, Book::getTitle));


    However you can create inline map using Collectors.toMap

    Map<String,String>bookinlineMap=Stream.of(
    new Book("isbn3","title3");
    // more objects if required
    ).collect(Collectors.toMap(Book::getIsbn, Book::getTitle));

    Book class method getIsbn and getTitle are non static method , however they are referred in static way Book::getIsbn and Book::getTitle . Because, when you iterate a collection, like the one above, the Java Stream will smartly replace the Class Name with the instance of current object. Hence the call actually happens on the nth Book in the list – where n is the index of current iteration.

     Convert Entrie object to Map Value List<V> to Map<K,V>  using Stream Collectors.toMap() method


    In the above example only isbn and title member are used to create map entries. But in some situation you want to create Book object as value.

    public Class Book{
    private String isbn;
    private String title;
    private String author;
    private double price;

    // getter and setter 
    // public Book(String isbn,String title,String author, double price){
    this.isbn=isbn;
    this.title=title;
    this.author=author;
    this.price=price;
    }
    }

    In this case we use following

    Map<String,Book> map=bookList.stream().collect(Collectors.toMap(Book::getIsbn, Function.identity()));

    Here, we have used Function.identity() to refer to the actual Book instance. Where the identity() method returns a function that returns the input object.

    How to group by a Field using Stream groupingBy

    Many a times, we have to iterate through a Collection and group by id or any other field. To demonstrate we will iterate the above list of books to group by books with same name.
    List<bookList> ————> Map<String, List<Book>>

    Map<String, List<Book>> groupedByName = bookList.stream().collect(Collectors.groupingBy(Book::getTitle));

    Here, we have used Collectors.groupingBy and passed a Method Reference to users name.
    And, the output we get:


    Using Guava Library to Create HashMap

    Using Google’s Guava Library, you can create and initialize maps inline.

    Immutable Map using Google Guava

    Map<String, String> map = ImmutableMap.of("country""India""capital""Delhi""Flag""Tricolor");

    However, it creates an Immutable map of n key/value pairs. The of function takes varargs and you can pass any number of entries inline.
    There is a way to create a Mutable map using Google Guava.

    Mutable Map using Google Guava

    Map<String, String>ImmutableMap 
                                           =ImmutableMap.of("country""India""capital""Delhi""Flag""Tricolor"

    Map<String, String> mutuableMap = Maps.newHashMap(immutableMap);

    However, it creates two different maps. Firstly create an immutable map with the entries and then create a mutable map.

    HashMap/ Map does not have iterator itself.

    1. Iterating Key of HashMap

    • Via key Iterator using Set<K> keySet() method 
    • Via for-each loop
    • Via a Stream
     Via Key Iterator using Set<K> keySet() method 

    you can iterate all the key of Map using keySet() method, It return Set<K> from this we get iterator of the set following example demonstrate.

    public static void main(String[] args) {
    Map<String,String> map=new HashMap<>();
    map.put("Country", "India");
    map.put("Capital", "Delhi");
    map.put("Population", "125M");
    Iterator<String> iterator =map.keySet().iterator();
    while(iterator.hasNext()) {
    String key=iterator.next();
    String value =map.get(key);
    System.out.println(key +" :" +value);
    }
    }
    //output 
    Country :India
    Population :125M
    Capital :Delhi

    Via For-Each loop

    from Java 5 you can use for each loop to iterate Map .

    for example :

    public static void main(String[] args) {
    Map<String,String> map=new HashMap<>();
    map.put("Country", "India");
    map.put("Capital", "Delhi");
    map.put("Population", "125M");
    for(String key : map.keySet()) {
    System.out.println(key +":"+map.get(key));
    }
    }

    // output
    Country :India
    Population :125M
    Capital :Delhi

    Via Stream
    From Java 8 you can use Java Stream to iterate key of the Map. First obtain keySet from Map then obtain stream from keySet .
    Following example demonstrate this

    public static void main(String[] args) {
    Map<String,String> map=new HashMap<>();
    map.put("Country", "India");
    map.put("Capital", "Delhi");
    map.put("Population", "125M");

    Stream<String> stream=map.keySet().stream();
    stream.forEach(key ->System.out.println(key+":"+map.get(key)));
    //map.keySet().stream().forEach(System.out::println);
    }

    // output

    Country :India
    Population :125M
    Capital :Delhi

    2. Iterating the Value of Map

    • Via values() method
    • via for-each
    • via stream

    public static void main(String[] args) {
    Map<String,String> map=new HashMap<>();
    map.put("Country", "India");
    map.put("Capital", "Delhi");
    map.put("Population", "125M");
    /*
     * Iterator<String> iterator =map.keySet().iterator(); while(iterator.hasNext())
     * { String key=iterator.next(); String value =map.get(key);
     * System.out.println(key +" :" +value); }
     */
    /*
     * for(String key : map.keySet()) { System.out.println(key +":"+map.get(key)); }
     */
    /*
     * Stream<String> stream=map.keySet().stream(); stream.forEach(key
     * ->System.out.println(key+":"+map.get(key)));
     */
    //Via Iterator
    Iterator<String> value=map.values().iterator();
    while(value.hasNext())
    System.out.println(value.next());
    //via For-each
    for(String viaForEachValue :map.values()) {
    System.out.println(viaForEachValue);
    }
    //Via Stream
    map.values().stream().forEach(viaStreamValue->System.out.println(viaStreamValue));
    }


    3. Iterating Entries of HashMap

    • Using Iterator
    • Using for-each loop
    • Using JAVA 8
    Using Iterator

    public class Student{
    private int id;
    private String firstName;
    private String lastName;
    private String zipCode;
    //accessor and mutator methods
    // constructor
    ......
    }
    List<Student> students=new ArrayList<>();
    students.add(new Student(1 ,"Yogesh","Kumar","110010"));
    students.add(new Student(2, "Mohan","P","111011"));


    Map<Integer,Student> map=students.stream().collect(Collectors.toMap(Student::getId, Function.identity());

    Iterator<Map.Entry<Integer,Student>> iterator = map.entrySet().iterator();
    while(iterator.hasNext()){
    Map.Entry<Integer,Student> mapEntry=iterator.next();
    Integer key=mapEntry.getKey();
    Student student=mapEntry.getValue();

    }

    for(Map.Entry<Integer,Student> mapEntry : map.entrySet()){

    Integer key=mapEntry.getKey();
    Student student=mapEntry.getValue();
    }

    map.stream().forEach((k,v)->{System.out.println(k +":"+v)});

    map.entrySet().forEach(System.out::println); //method reference;

    HashMaps and Multi Threading.

    Working with multi-threading is always tricky. But, when it comes to HashMaps along with multi threading, it is straightforward. However, you need to know few of the basics. Let’s cover those basics here.
    HashMaps are not synchronized. This means, you can actually have multiple threads reading and writing from same Hashmap.
    For example, consider you have a thread that is iterating a HashMap of size 10. Meanwhile, another thread removes an element from the Hashmap and the size now became 9. This can cause the iteration logic go on toss. To make it easier the iterators are made fail-fast. In other words, when the iterator senses modification of the underlying HashMap, they immedietly throw ConcurrentModificationException.
    This behavior is really helpful, as you can rely on the application to fail and hence no need to worry about having dirty reads. Although, if snychronization between threads is really important for you, you can still synchnronize the blocks or the objects accessing the HashMaps.
    Alternatevly, you can use a Synchrnoized copy of your Hashmap. Refer to below example to learn how to get a synchronized copy of your HashMap.
    1. Map synchronizedMap = Collections.synchronizedMap(hashMap);

    The synchronizedMap is a synchrnozed copy of your map. You can use this map safely with the threads. However, remember this is a copy of your existing hashmap. Therefore, if you have a really big hashmap this will be expensive on the memory.


    When to use HashMaps

    HashMaps have variety of usages. Having a key value structure they can be used to store many different type of elements. They are useful, when you do not have to worry about sorting or ordering.
    Consider, you have a properties file to read and keep in memory whenever you application wants to access any property. You can read the file once and store all they key value pairs in a HashMap and keep the map accessible to your code. Then your application can query the map with a specific key and access the associated value in constant amount of time.
    Moreover, because of its structure it can be used to hold entire database table in a List<Map<String, Object>>. Where each map in the list represent entire row of a table. Similarly, it can also used to generically hold entire request body in a web application.
    Additionally, in below example we will create an User instance and map it to a HashMap using fasterxml library.

    1. User user = new User(1L, "Arya""Stark"14);
    2. ObjectMapper objectMapper = new ObjectMapper();
    3. // Covert object to a Map
    4. Map<String, Object> objectMap = objectMapper.convertValue(user, Map.class);
    5. System.out.println(objectMap);
    6. // Output:
    7. // {id=1, name=Arya, lastName=Stark, age=14}

    No comments:

    Post a Comment