Java HashMap vs Hashtable
0 CommentsThe 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 thanHashMap
if used in single threaded environment. HashMap
allows onenull
key and multiplenull
values whereasHashtable
doesn’t allownull
values.HashMap
is traversed byIterator
whileHashtable
can be traversed by bothIterator
and the legacyEnumeration
.Iterator
inHashMap
is a fail-fast iterator. It throwsConcurrentModificationException
if any thread other than the iterator’sremove()
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, TheEnumeration
returned byHashtable
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.
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.
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.
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.