Java HashMap vs Hashtable

Java HashMap vs Hashtable

0 Comments

The Java collection framework provides several collection implementations to store and operate on objects. Some groups of implementations are similar in the operations they perform, and it is common for novice programmers to use them interchangeably.

ArrayList and LinkedList are two such implementations that are often confused and incorrectly applied in applications. I have written a post on ArrayList vs LinkedList that explains the nuances of them.

In this post, I’ll take up two other implementations that although appears identical, but are inherently different. They are HashMap and Hashtable.

HashMap and Hashtable

Both HashMap and HashTable implements the Map interface, a sub interface of the Collection interface. A Map stores key-value pairs where duplicate keys are not allowed.

HashMap extends the AbstractMap class and implements the Map interface. On the other hand, Hashtable inherits the Dictionary class and also implements the Map interface.

As both Hashtable and HashMap implements Map, they are similar as both stores key-value pairs where the keys are unique and stored as hash values. To store an element in a Hashtable or HashMap, you need to specify a key object and its associated value. The key is then hashed, and the resulting hash code is used as the index at which the value is stored within the table.

HashMap vs Hashtable

The primary difference between HashMap and Hashtable is that HashMap is not thread-safe, and therefore cannot be shared between multiple threads without external synchronization. On the other hand, Hashtable is thread safe, and therefore can be shared between multiple threads.

Some other key differences are:

  • Because of synchronization and thread safety, Hashtable is much slower than HashMap if used in single threaded environment.
  • HashMap allows one null key and multiple null values whereas Hashtable doesn’t allow null values.
  • HashMap is traversed by Iterator while Hashtable can be traversed by both Iterator and the legacy Enumeration.
  • Iterator in HashMap is a fail-fast iterator. It throws ConcurrentModificationException if any thread other than the iterator’s remove() method tries to modify the map structurally. Thus, in the face of concurrent modification, the iterator fails fast and cleanly, rather than risking undesirable behaviour. However, The Enumeration returned by Hashtable doesn’t have this behaviour.

In addition to these differences, one commonly asked question is Why HashMap stores one null key but Hashtable does not?

HashMap, allows storing one null key and multiple null values. The reason for allowing only one null key is because keys in a HashMap has to be unique. On the other hand Hashtable does not allow null keys. This is because the objects used as keys in a Hashtable implements the hashCode() and equals() methods for their storage and retrieval. Since null is not an object it cannot implement the methods. If you try hashing a null key, it will throw a NullPointerException.

Testing Operations on HashMap and Hastable

Let us perform some operations on HashMap and Hashtable. We will start with an Item POJO that we will store in both the implementations.

The Item POJO class is this.

Item.java
package springframework.guru.hashmapvshashtable.model;

public class Item {
   private String item;
   private int price;

   public Item() {
   }

   public Item(String item, int price) {
       this.item = item;
       this.price = price;
   }

   public String getItem() {
       return item;
   }

   public void setItem(String item) {
       this.item = item;
   }

   public int getPrice() {
       return price;
   }

   public void setPrice(int price) {
       this.price = price;
   }

   @Override
   public String toString() {
       return "Item{" +
               "item='" + item + '\'' +
               ", price=" + price +
               '}';
   }

   @Override
   public int hashCode() {
       int hashcode = 0;
       hashcode = price * 20;
       hashcode += item.hashCode();
       return hashcode;
   }

   @Override
   public boolean equals(Object obj) {
       if(obj instanceof Item) {
           Item i = (Item)obj;
           return (i.item.equals(this.item) && i.price == this.price);
       }else {
           return false;
       }
   }
}

The preceding code example created an Item POJO. The class overrides the hashCode() method. The hash code implementation generates unique hash code for each object based on their state. This means if you have objects with the same state, you will get the same hash code. If you are overriding hashCode() you need to override the equals() method also, as present in the preceding code.

I will use JUnit to test operations on both the HashMap and Hashtable implementations. If you are new to JUnit, I suggest going through my series on JUnit.

The ItemTest JUnit test class where I will keep adding test cases is this.

ItemTest.java
package springframework.guru.hashmapvshashtable.model;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import static junit.framework.TestCase.*;


public class ItemTest {

    private Item item, item1,item2,item3;

    @Before
    public void setUp() {
        item = new Item("Water bottle", 20);
        item1 = new Item("Plate",30);
        item2 = new Item("Spoon",10);
        item3 = new Item("Glass", 15);
    }
    @After
    public void tearDown() {
        item = item1 = item2 = item3 = null;
   }

    @Test
    public void ItemWithHashMapTest() {
        HashMap<Item, Integer> hashMap = new HashMap<Item, Integer>();
        hashMap.put(item, 10);
        hashMap.put(item1, 5);
        hashMap.put(item2, 10);
        hashMap.put(item3, 3);
        System.out.println("Size of HashMap "+ hashMap.size());
        assertEquals(4,hashMap.size());
        for(Map.Entry entry: hashMap.entrySet()) {
            System.out.println(entry.getKey().toString() + "-" + entry.getValue());
        }
        Item newItemAsKeyInHashMap = new Item("Water bottle", 20);
        assertTrue(hashMap.containsKey(newItemAsKeyInHashMap));
        assertTrue(hashMap.containsValue(hashMap.get(newItemAsKeyInHashMap)));
    }

}

The test case in the preceding code creates a HashMap<Item, Integer> object and initializes it with Item objects. The assertEquals() method tests the number of elements in the HashMap. The code iterates through the HashMap to print the key value pairs of the Hashmap. Both the AssertTrue() methods checks for the presence of an Item key and its value.

The output on running the tests in IntelliJ is this.

Test Output HashMap Operation.png

Next, let’s write a test case to test the same operations on a Hashtable.

@Test
public void ItemWithHashtableTest() {
    Hashtable<Item, Integer> hashtable = new Hashtable<Item, Integer>();
    hashtable.put(item, 10);
    hashtable.put(item1, 5);
    hashtable.put(item2, 10);
    hashtable.put(item3, 3);
    /*Will not be allowed being duplicate*/
    hashtable.put(item1,16);
    assertEquals(4, hashtable.size());
    System.out.println("Size of Hashtable "+ hashtable.size());
    for(Map.Entry entry: hashtable.entrySet()) {
        System.out.println(entry.getKey().toString() + "-" + entry.getValue());
    }
    Item itemAsKeyInHashtable = new Item("Water bottle", 20);
    assertTrue(hashtable.containsKey(itemAsKeyInHashtable));
    assertTrue(hashtable.containsValue(hashtable.get(itemAsKeyInHashtable)));
}

The output on running the tests in IntelliJ is this.
Test Output Hashtable Operation

Testing Concurrent Modifications

A common exception encountered when working with Java collection classes is ConcurrentModificationException. This exception is thrown while iterating through a collection if any thread other than the Iterator tries to modify the collection.

The following test attempts to remove an element while looping through the elements of a HashMap using a for loop.

@Test(expected = ConcurrentModificationException.class)
public void ConcurrentModificationExceptionTest() {
    HashMap<Item, Integer> hashMap = new HashMap<Item, Integer>();
    hashMap.put(item, 10);
    hashMap.put(item1, 5);
    hashMap.put(item2, 10);
    for(Map.Entry<Item, Integer> entry: hashMap.entrySet()) {
        Integer value = entry.getValue();
        if(value == 5) {
            hashMap.remove(entry.getKey());
        }
    }
}

The preceding test case throws an exception of type ConcurrentModificationException. The test case passes because it is expecting an exception of type ConcurrentModificationException

To remove an element while iterating through a Hashmap, ensure you use the remove() method of Iterator.

The following test attempts to remove an element while iterating through a HashMap using an Iterator.

@Test
public void ConcurrentModificationHashMapIterationTest() {
    HashMap<Item, Integer> hashMap = new HashMap<Item, Integer>();
    hashMap.put(item, 10);
    hashMap.put(item1, 5);
    hashMap.put(item2, 10);
    assertEquals(3,hashMap.size());
    for(Iterator<Map.Entry<Item, Integer>> it = hashMap.entrySet().iterator(); it.hasNext();) {
        Map.Entry<Item, Integer> entry = it.next();
        Integer value = entry.getValue();
        if(value == 5) {
            it.remove();
            System.out.println("Item with value 5 safely removed from HashMap");
        }
    }
    assertEquals(2,hashMap.size());
}

The test case passes and the output is this.

The following test expects a ConcurrentModificationException while trying to remove an element from a Hashtable while looping through its elements.

@Test(expected=ConcurrentModificationException.class)
public void ExceptionWithHashtableTest() {
    Hashtable<Item, Integer> hashtable = new Hashtable<Item, Integer>();
    hashtable.put(item, 10);
    hashtable.put(item1, 5);
    hashtable.put(item2, 10);
    for (Map.Entry<Item, Integer> entry : hashtable.entrySet()) {
        Integer value = entry.getValue();
         if (value == 5) {
            hashtable.remove(entry.getKey());
        }
    }
}

Hashtable supports both Enumeration and Iterator. As an Enumeration unlike Iterator is not fail-fast, you can use it to enumerate over the elements of a Hashtable and modify it.

The following test code removes an element while enumerating over a Hashtable.

public void ConcurrentModificationWithHashtableEnumerationTest() {
    Hashtable<Item, Integer> hashtable = new Hashtable<Item, Integer>();
    hashtable.put(item, 5);
    hashtable.put(item1, 10);
    hashtable.put(item2, 15);
    assertEquals(3,hashtable.size());
    Enumeration<Item> en = hashtable.keys();
    while(en.hasMoreElements()) {
        Item key = en.nextElement();
        Integer value = hashtable.get(key);
        System.out.println(key + "-" + value);
        if(key.getItem().equals("Plate")) {
            hashtable.remove(key);
            System.out.println("Item "+key+" safely removed from Hashtable");
        }
    }
   assertEquals(2,hashtable.size());
}

The output on running the test is this.

Testing for Null Values

HashMap allows only one null key but multiple null values.
The code to test a HashMap for null values is this.

@Test
public void NullCheckInHashMapTest() {
    HashMap<Item, Integer> hashMap = new HashMap<Item, Integer>();
    hashMap.put(item, 10);
    hashMap.put(item1, null);
    hashMap.put(null, 15);
    hashMap.put(null, null);
    for(Map.Entry entry: hashMap.entrySet()) {
        System.out.println(entry.getKey() + "-" + entry.getValue());
    }
    assertEquals(3, hashMap.size());
    assertNull(null,hashMap.get(null));
    System.out.println("Null key :"+ hashMap.get(null));
}

In this code, the assertEquals() method asserts that the HashMap contains three elements. Note that the code tries adding four elements out of which two elements have null keys.

The assertNull assertion asserts that the last added element with a null key is retained by the Hashtable.

The output on running the test is this.
Test Output HashMap Null Check

Hashtable on the other hand does not allow null keys.
The test code is this.

@Test(expected = NullPointerException.class)
public void NullCheckInHashtableTest() {
    Hashtable<Item, Integer> hashtable = new Hashtable<Item, Integer>();
    hashtable.put(item, 10);
    hashtable.put(item1, 5);
    hashtable.put(null, null);
    System.out.println("Hashtable Null key "+ hashtable.get(null));
}

The preceding test passes as it expects a NullPointerException to be thrown in Line * that attempts to add an element with a null key.

Summary

While still supported, Hashtable is considered obsolete and HashMap is the most direct replacement of it.

One difference that could impact your decision on selecting one between the two is that all relevant methods of Hashtable are synchronized while they are not in a HashMap. However the synchronization concern of HashMap is addressed by ConcurrenthashMap. But ConcurrenthashMap, unlike HashMap does not allow null to be used as a key or value.

In your application, you should not use Hashtable in new development. However, you will find lots of legacy code bases with Hashtable implementations and sooner or later, you would need working with them.

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like