HashMap 101

HashMap is fast.

What is HashMap in Java?

key-> value

null as a key

mapDemo.get(null);

key cannot be dumplicate

value can be duplicate

mapDemo.put(1, A); mapDemo.put(2, A);

equals() and hashCode()

Initailise a HashMap

see: Initialize a HashMap in Java from Bealdung.

  1. From a static block (recommanded)

    static HashMap is mutable, you can add or remove element later.

     public static Map<String, String> articleMapOne;
     static {
         articleMapOne = new HashMap<>();
         articleMapOne.put("ar01", "Intro to Map");
         articleMapOne.put("ar02", "Some article");
     }
    
     Map<String, String> myMap = createMap();
    
     private static Map<String, String> createMap() {
         Map<String,String> myMap = new HashMap<String,String>();
         myMap.put("a", "b");
         myMap.put("c", "d");
         return myMap;
     }
    
  2. Initialize the map using the double-brace syntax (avoid)

    • It generates an additional class which increases memory consumption, disk space consumption and startup-time
    • In case of a non-static method: It holds a reference to the object the creating method was called upon. That means the object of the outer class cannot be garbage collected while the created map object is still referenced, thus blocking additional memory
     Map<String, String> doubleBraceMap  = new HashMap<String, String>() ;
    
  3. Use Collections.singletonMap Immutable, throws java.lang.UnsupportedOperationException

     public static Map<String, String> createSingletonMap() {
         return Collections.singletonMap("username1", "password1");
     }
    
  4. Use Java8 Stream Collectors.toMap

     @Test
     public  void testCollectorsToMap() {
    
         Map<String, Garden> gardenMap = Stream.of(new Object[][]{
                 {"firstGarden", new Garden(1L, "first")},
                 {"secondGarden", new Garden(2L, "second")}
         }).collect(Collectors.toMap(data -> (String)data[0], 
                                     data -> (Garden)data[1]));
    
         Assert.isTrue(gardenMap.size() == 2,"size is 2");
         Assert.isTrue(gardenMap.**get**("firstGarden").getName().equals("first"), "first garden name is first");
     }
    
  5. Use Map.of()

    • supports only a maximum of 10 key-value pairs
    • immutable
     Map<String, String> map = Map.of("key1","value1", "key2", "value2");
    

    Adding elements after initialization, you need to create a new HashMap:

     Map<String, String> map = new HashMap<String, String> (
         Map.of("key1","value1", "key2", "value2"));
    
  6. Map.ofEntries()

    • It’s similar to the Map.of() but has no limitations on the number of key-value pairs
    • immutable
    • do not allow null keys or duplicate keys
     Map<String, String> map = Map.ofEntries(
         new AbstractMap.SimpleEntry<String, String>("name", "John"),
         new AbstractMap.SimpleEntry<String, String>("city", "budapest"),
         new AbstractMap.SimpleEntry<String, String>("zip", "000000"),
         new AbstractMap.SimpleEntry<String, String>("home", "1231231231")
     );
    

    Adding elements after initialization, you need to create a new HashMap:

     Map<String, String> map2 = new HashMap<String, String> (
         Map.ofEntries(
             new AbstractMap.SimpleEntry<String, String>("name", "John"),    
             new AbstractMap.SimpleEntry<String, String>("city", "budapest")));
    

HashMap Methods

Put and Get

  1. Put and Get

     @Test
     public  void testHashMapPutAndGet() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         gardenMap.put("firstGarden", new Garden(1L, "first"));
            
         final Garden firstGarden = gardenMap.get("firstGarden");
        
         Assert.isTrue(firstGarden.getId().equals(1L), "first garden id is 1L");
         Assert.isTrue(firstGarden.getName().equals("first"), "first garden name is first");
     }
    
  2. putIfAbsent()

     @Test
     public  void testHashMapPutIfAbsent() {
    
         Map<String, Garden> gardenMap = new HashMap<>();
         gardenMap.putIfAbsent("firstGarden", new Garden(1L, "first"));
         gardenMap.putIfAbsent("firstGarden", new Garden(2L, "second"));
    
         final Garden firstGarden = gardenMap.get("firstGarden");
    
         Assert.isTrue(firstGarden.getId().equals(1L), "first garden id is 1L");
         Assert.isTrue(firstGarden.getName().equals("first"), "first garden name is first");
     }
    
  3. getOrDefault()

     @Test
     public  void testHashMapGetOrDefault() {
    
         Map<String, Garden> gardenMap = new HashMap<>();
         gardenMap.putIfAbsent("firstGarden", new Garden(1L, "first"));
    
         final Garden secondGarden = gardenMap.get("second");
         Assert.isNull(secondGarden, "It does not exists yet");
    
         Garden defaultGarden = new Garden(0L, "default");
         final Garden second = gardenMap.getOrDefault("second", defaultGarden);
    
         Assert.isTrue(second.getId().equals(0L), "default garden id is 0L");
         Assert.isTrue(second.getName().equals("default"), "default garden name is default");
     }
    

remove a value

  1. Remove from key
  2. Cannot remove null as key
  3. Can remove an entrySet, null as key

     @Test
     public void testHashMapRemove() {
    
         Map<String, Garden> gardenMap = new HashMap<>();
         gardenMap.put("firstGarden", new Garden(1L, "first"));
    
         Garden nullGarden = new Garden(2L, "null");
         gardenMap.put(null, nullGarden);
    
         gardenMap.remove("firstGarden");
         Assert.isTrue(gardenMap.size() == 1, "1 left");
    
         gardenMap.remove(null); //java.lang.IllegalArgumentException: cannot remove null
    
         gardenMap.remove(null, nullGarden);
         Assert.isTrue(gardenMap.size() == 0, "finally gone");
     }
    

Contains

  1. ContainsKey
  2. ContainsValue

     @Test
     public void testHashMapContains() {
    
         Map<String, Garden> gardenMap = new HashMap<>();
         gardenMap.put("firstGarden", null);
         final  Garden nullGarden = new Garden(0L , "null");
         gardenMap.put(null, nullGarden);
    
         Assert.isTrue( gardenMap.containsKey("firstGarden"), "contains firstGarden key");
         Assert.isTrue( gardenMap.containsValue(nullGarden), "contains nullGarden");
         Assert.isTrue( gardenMap.containsKey(null), "contains null key");
         Assert.isTrue( gardenMap.containsValue(null), "contains null value");
     }
    

merge() and compute()

//todo

Iterating over a HashMap

  1. iterating keys

     @Test
     public void testHashMapIterateKeys() {
    
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
    
         for (String key : gardenMap.keySet()) {
    
             if (key == null) {
                 Garden theGarden = gardenMap.get(null);
                 Assert.isTrue( theGarden == nullGarden, "got null garden");
    
                 gardenMap.put(null, firstGarden);
             }
         }
         Assert.isTrue( gardenMap.get(null) == firstGarden, "null garden is replaced");
     }
    
  2. iterating values

     @Test
     public void testHashMapIterateValues() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
        
         for (Garden garden : gardenMap.values()) {
        
             if (garden == nullGarden) {
                 garden.setId(3L);
             }
         }
         Assert.isTrue( gardenMap.get(null).getId() == 3L, "null garden is updated");
     }
    
  3. iterating entries

     @Test
     public void testHashMapIterateEntries() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
        
         for (Map.Entry<String, Garden> entry : gardenMap.entrySet()) {
        
             if (entry.getKey() == null) {
                 Assert.isTrue(entry.getValue() == nullGarden, "here is the null garden");
                 entry.getValue().setId(3L);
             }
         }
         Assert.isTrue( gardenMap.get(null).getId() == 3L, "null garden is updated");
     }
    
  4. iterator avoids ConcurrentModificationException

     @Test
     public void testHashMapIterator() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
        
         Iterator it = gardenMap.entrySet().iterator();
         while (it.hasNext()) {
             Map.Entry entry = (Map.Entry)it.next();
        
             if (entry.getKey() == null) {
                 Garden theGarden = (Garden) entry.getValue();
                 Assert.isTrue( theGarden.getId() == 0L, "null garden id is 0");
                 it.remove(); // avoids a ConcurrentModificationException
             }
         }
         Assert.isTrue( gardenMap.size() == 2, "null garden is removed");
     }
    

Copying a HashMap

  1. Shallow copy copies reference. Change on one side will affect other side.
  2. Deep copy creates new object. Change on one side will NOT affect other side.

Shallow copy

  1. Using the HashMap Constructor

     @Test
     public void testHashMapConstructorCopy() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
        
         HashMap<String, Garden> shallowCopy = new HashMap<>(gardenMap);
         Assert.isTrue( shallowCopy.size() == 3, "shallow copy has same size");
        
         nullGarden.setId(3L);
         Assert.isTrue( shallowCopy.get(null).getId() == 3L, "shallow copy is also updated");
     }
    
  2. Using Map.put() copy

     @Test
     public void testHashMapPutCopy() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
        
         HashMap<String, Garden> shallowCopy = new HashMap<>();
        
         gardenMap.entrySet()
                 .forEach(entry -> shallowCopy.put(entry.getKey(), entry.getValue()));
        
         Assert.isTrue( shallowCopy.size() == 3, "shallow copy has same size");
        
         nullGarden.setId(3L);
         Assert.isTrue( shallowCopy.get(null).getId() == 3L, "shallow copy is also updated");
     }
    
  3. Using Map.putAll() copy

     @Test
     public void testHashMapPutAllCopy() {
        
         Map<String, Garden> gardenMap = new HashMap<>();
         final  Garden nullGarden = new Garden(0L , "null");
         final  Garden firstGarden = new Garden(1L , "first");
         final  Garden secondGarden = new Garden(2L , "second");
         gardenMap.put(null, nullGarden);
         gardenMap.put("firstGarden", firstGarden);
         gardenMap.put("secondGarden", secondGarden);
        
         HashMap<String, Garden> shallowCopy = new HashMap<>();
        
         shallowCopy.putAll(gardenMap);
        
         Assert.isTrue( shallowCopy.size() == 3, "shallow copy has same size");
        
         nullGarden.setId(3L);
         Assert.isTrue( shallowCopy.get(null).getId() == 3L, "shallow copy is also updated");
     }
    

Deep copy

//todo Now, Java doesn’t have any built-in deep copy implementations. So to make a deep copy, either we can override the clone() method or use a serialization-deserialization technique.

Apache Commons has SerializationUtils with a clone() method to create a deep copy. For this, any class to be included in deep copy must implement the Serializable interface:

Linkedhashmap

//todo

Concurrent HashMap

  1. ConcurrentHashMap key cannot be null

      @Test
     public void testConcurrentHashMapKeyNull() {
    
         Map<String, Garden> gardenMap = new ConcurrentHashMap<>();
    
         try {
             gardenMap.put(null, new Garden());
         } catch (NullPointerException ex) {
         }
         Assert.isTrue(gardenMap.size() == 0, "still empty");
     }
    
  2. ConcurrentHashMap allows you add and remove an entry while iterating. HashMap won’t allow.

     @Test
     public void testConcurrentHashMapRemove() {
    
    
         Map<String, Garden> gardenMap = new ConcurrentHashMap<>();
         gardenMap.put("firstGarden", new Garden(1L , "first"));
         gardenMap.put("secondGarden", new Garden(2L , "second"));
    
         gardenMap.entrySet()
                 .forEach(entry -> {
                     if (entry.getKey().equalsIgnoreCase("firstGarden")) {
                         gardenMap.remove("firstGarden");
                     }
                 });
    
         Assert.isTrue( gardenMap.size() == 1, "first garden is gone");
     }
    
  3. ConcurrentHashMap allows you to add or move entries in one thred, and you can see it in another thread.

     @Test
     public void testConcurrentHashMapTwoThread() throws Exception {
        
         Map<String, Garden> gardenMap = new ConcurrentHashMap<>();
         gardenMap.put("firstGarden", new Garden(1L, "first"));
         gardenMap.put("secondGarden", new Garden(2L, "second"));
        
         Thread thread1 = new Thread(new Runnable() {
             public void run() {
                 try {
                     Thread.sleep(50);
                     gardenMap.put("third garden", new Garden());
                 } catch (InterruptedException e) {
                 }
             }
         });
        
         Thread thread2 = new Thread(new Runnable() {
             public void run() {
                 try {
                     Thread.sleep(70);
                     Assert.isTrue(gardenMap.size() == 3, "we got one from other theads");
                 } catch (InterruptedException e) {
                 }
             }
         });
        
         thread1.start();
         thread2.start();
         Thread.sleep(100);
        
         Assert.isTrue(gardenMap.size() == 3, "3 entries now");
     }
    

References

  1. Initialize a HashMap in Java
  2. Copying a HashMap in Java

Tags:

Posted: