Complete Guide to HashMap.

1. Introduction

In this article, we will discuss HashMap class and its methods. If you are looking for Map interface’s enhancements of Java 8, 9, and 10, you can refer to this article here. This article describes all default methods and static factory methods with examples.

2. Content 

In this article we will look at HashMap class’s 4 constructors and 14 methods. If you want to understand what HashMap really is and why it is beneficial, read this article. This article will provide a thorough analysis of different constructors and methods of HashMap class. The methods of this class are actually from the Map<K, V> interface. HashMap<K, V> class implements, Map<K, V> interface.

Below is the hierarchy of HashMap class.

HashMap Hierarchy

Map<K, V> interface defines a contract about a key-value mapping which is implemented partially by AbstractMap<K, V> class. The contact is completely implemented by HashMap<K, V> class.

Why do we need HashMap?

HashMap<K, V> is used to store Key-Value pairs in an underlying container. The best part about this data structure is it allows contact time access, O(1), insertion and retrieval. Remember the contact time access is subject to implementation of hashCode() method. If the hashCode() method’s implementation is poor, the time complexity for insertion and retrieval may deteriorate.

3. Constructor: public HashMap()

public HashMap() is the simple constructor of the HashMap class that accepts no arguments. 

You can create an instance of HashMap like this : 

Map<Long, Transaction> transactions = new HashMap<>();

There are two most important parameters that can affect the performance of HashMap. They are load factor and initial capacity. In this constructor, we do not specify any of those parameters. We trust the HashMap class to decide on these values. 

Example : 

@Test
public void hashMapNoArgs() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	Assert.assertEquals(txnDataSet.size(), transactions.size());
}

4. Constructor: public HashMap(int initialCapacity)

public HashMap(int initialCapacity) constructor is used to create the HashMap instance with number_of_buckets = initialCapacity. Bucket is an index of an array where the data will be stored. It computes the index using a hash function.

The default initialCapacity used by the HashMap class is 16. Which means it creates an array of length 16 when you try to insert the first element. 

I will explain in the next section(HashMap(int initialCapacity, float loadFactor) constructor) on why it is a good idea not to tinker with this parameter.

@Test
public void hashMapInitialCapacity() {

	int initialCapacity = 20;
	Map<Long, Transaction> transactions 
							= new HashMap<>(initialCapacity);

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	Assert.assertEquals(txnDataSet.size(), transactions.size());
}

5. public HashMap(int initialCapacity, float loadFactor)

public HashMap(int initialCapacity, float loadFactor) constructor is used to provide initialCapacity and loadFactor for the Map.

We discussed in the previous section about initialCapacity. It is the number of buckets we want to create for our hash table. The loadFactor means how full the hash table is when we need to expand it and rehash all data from the old hash table to the new hash table.

Let us take an example : 

initialCapacity = 16

loadFactor = 0.75

This means we have created a hash table of length 16. The load factor 0.75 means that 0.75 * 16 = 12, once we add 12 Key-Value pairs, to this hash table resizing needs will add new Key-Values pairs to maintain the load factor.

Then the HashMap class will create a new hash table of size 32, i.e. initialCapacity * 2. The load factor will remain the same. HashMap class will remap/rehash all Key-Value pairs into a new table.

New size = 32

load Factor = 0.75

Now when the total Key-Value pairs added are 0.75 * 32 = 24, resizing and rehashing will happen again.

You must exercise caution when using this constructor. If you end up providing a bad load factor, then resizing may happen too often or too rarely. This will affect the performance of HashMap. It is better to not use this constructor with half-baked knowledge. If you want to use this constructor, then prove yourself by running benchmarks and see what it is worth.

@Test
public void hashMapInitialCapacityWithLoadFactor() {

	int initialCapacity = 16;
	float loadFactor = 0.75f;
	Map<Long, Transaction> transactions 
					= new HashMap<>(initialCapacity, loadFactor);

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	Assert.assertEquals(txnDataSet.size(), transactions.size());
} 

6. Constructor: public HashMap(Map<? extends K, ? extends V> m)

public HashMap(Map<? extends K, ? extends V> m) is a kind of copy constructor for HashMap class. It accepts any implementation of Map. The input Map’s data is inserted into this HashMap. 

It does not change the input Map m. This constructor will throw a NullPointerException if the input Map m is null.

Example 1: We have input as List<Transaction>. From this we create a Map<Long, Transaction> with transaction id as Key and Transaction as Value.

@Test
public void hashMapCopy() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	// initialCapacity and loadFactor is 16 and 0.75f
	Map<Long, Transaction> txnsCopy = new HashMap<>(transactions);
	Assert.assertEquals(transactions.size(), txnsCopy.size());
}

Example 2: If the input Map is null, the constructor throws NullPointerException.

@Test(expected = NullPointerException.class)
public void hashMapCopyNPE() {
	Map<Long, Transaction> transactions = new HashMap<>(null);
}

7. V put(K key, V value)

V put(K key, V value) method is used to insert Key of type K and Value of type V into the hash table. 

put method will return V i.e. Value of previously mapped Key. If there was already a Key present in this Map, then HashMap will replace the value for that Key with the new Value specified in the parameter. If there was no Key in HashMap, then the method returns null.

put method also allows one null key.

Example 1: We add transactionId as Key and transaction as Value in HashMap using the put method.

@Test
public void putMethod1() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Assert.assertEquals(transactions.size(), transactions.size());
}

Example 2: We add null as Key and “value1” as Value in HashMap. Then, we add null as Key and “value999” as value. 

@Test
public void putMethod2() {
	Map<Long, String> transactions = new HashMap<>();
	transactions.put(null, "value1");

	String previousValue = transactions.put(null, "value999");
	Assert.assertEquals("value1", previousValue);	
}

Example 3: put method can return null if the Key-Value mapping was not present. It can also return null if the mapping has null value. 

@Test
public void putMethod3() {
	Map<Long, String> transactions = new HashMap<>();
	transactions.put(null, null);

	String previousValue = transactions.put(null, "value999");
	Assert.assertEquals(null, previousValue);
}

8. V get(Object key)

V get(Object key) method is used to return the Value of the given Key. If no such mapping exists, then the method returns null. It can also return null if it maps the Key to null.

This method should not be used to detect whether a Key exists in HashMap. For that we should use the containKey(Object key) method.

Example 1: We query for the transactionId that already exists in HashMap. We get that record and we assert that the result is not null. 

@Test
public void getMethod1() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	Long transactionId = txnDataSet.get(0).transactionId();
	Transaction transaction = transactions.get(transactionId);
	Assert.assertNotNull(transaction.transactionId());
}

Example 2: HashMap allows one null Key. We query for the record that exists is in HashMap with null Key and expect it to return the record. 

@Test
public void getMethod2() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	transactions.put(null, txnDataSet.get(0));
	Transaction transaction = transactions.get(null);
	Assert.assertNotNull(transaction);
}

Example 3: HashMap allows one null Key and multiple null values. Here the get() method returns null. It doesn’t mean that record doesn’t exist, it just means that the record exists and is null.

@Test
public void getMethod3() {
	Map<Long, Transaction> transactions = new HashMap<>();

	transactions.put(null, null);
	Transaction transaction = transactions.get(null);
	Assert.assertNull(transaction);
}

Example 4: We search for the transactionId that doesn’t exist in HashMap. Here, the get method returns null. It means that the Key doesn’t exist in HashMap.

@Test
public void getMethod4() {
	Map<Long, Transaction> transactions = new HashMap<>();

	// This key doesn't exists.
Transaction transaction = transactions.get(Long.valueOf(23898234L));
	Assert.assertNull(transaction);
}

So now there is a confusion that if the get() method returns null does it mean that Key doesn’t exist or Key exists and Value is null? The answer to this question is to use containKey(Object key) method. Let us look at this method now.

9. boolean containsKey(Object key)

boolean containsKey(Object key) method is used to determine if the Key is present in HashMap or not.

To get value out of HashMap, we use the get() method. But get(Object key) method returns null if the Key is not present or Key is present and Value is null. So you cannot say with certainty that Key is present or not.

To overcome this, what we can do is extract the value out in the following way.

Map<String, String> symbols = new HashMap<>();
symbols.put("some key", null);

// check if the key exists. 
// If yes, then only extract out the value.
if (symbols.containsKey("some key")) {
	symbols.get("some key");
}

Example 1: Search for the different Keys using containsKey method.

@Test
public void containsKey1() {
	Map<String, String> symbols = new HashMap<>();
	symbols.put("%", "Percent");
	symbols.put("*", "Asterisk");
	symbols.put("~", "Tilde");
	symbols.put("$", "Dollar");
	symbols.put("#", "Octothorpe");

	boolean containsAsterick = symbols.containsKey("*");
	Assert.assertTrue(containsAsterick);

	boolean containsOne = symbols.containsKey("1");
	Assert.assertFalse(containsOne);
}

Example 2: Search for Key that is null.

@Test
public void containsKey2() {
	Map<String, String> symbols = new HashMap<>();
	symbols.put(null, "some value");

	boolean containsNull = symbols.containsKey(null);
	Assert.assertTrue(containsNull);
}

10. boolean containsValue(Object value)

boolean containsValue(Object value) method returns true if the HashMap has one or more mapping to this value. This method takes linear time, as it needs to go through all the mappings in the HashMap.

Example 1:

@Test
public void containsValue1() {
	Map<String, String> symbols = new HashMap<>();
	symbols.put("%", "Percent");
	symbols.put("*", "Asterisk");
	symbols.put("~", "Tilde");
	symbols.put("$", "Dollar");
	symbols.put("#", "Octothorpe");

	boolean containsAsterick = symbols.containsValue("Asterisk");
	Assert.assertTrue(containsAsterick);

	boolean containsOne = symbols.containsValue("1");
	Assert.assertFalse(containsOne);
}

11. void putAll(Map<? extends K, ? extends V> m)

putAll(Map<? extends K, ? extends V> m) method is used to merge the Key-Value mappings from Map m to this Map.

Example 1: We create two different Maps. One Map contains children and other contains elders of House Stark.

@Test
public void putAll() {
	Map<String, String> children = new HashMap<>();
	children.put("Jon", "Snow");
	children.put("Brandon", "Stark");
	children.put("Arya", "Stark");
	children.put("Sansa", "Stark");
	children.put("Rickon", "Stark");

	Map<String, String> elders = new HashMap<>();
	elders.put("Eddard", "Stark");
	elders.put("Catlyn", "Stark");
	elders.put("Lyanna", "Stark");
	elders.put("Benjen", "Stark");

	Map<String, String> houseStark = new HashMap<>();
	houseStark.putAll(children);
	houseStark.putAll(elders);

	Assert.assertEquals(9, houseStark.size());
}

12. V remove(Object key)

V remove(Object key) method is used to remove the Key-Value mapping if the specified Key exists in HashMap.

If it removes the mapping, it returns the Value for this Key.

This method will return null if the Key doesn’t exist in Map.

Example 1: The Key is removed as it exists in the HashMap.

@Test
public void remove1() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Assert.assertEquals(txnDataSet.size(), transactions.size());

	long transactionId = txnDataSet.get(0).transactionId();
	Transaction txn = transactions.remove(transactionId);

	Assert.assertEquals(3, transactions.size());

}

Example 2: The Key is null, and it exists in the HashMap, hence it is removed.

@Test
public void remove2() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	transactions.put(null, txnDataSet.get(0));
	Assert.assertEquals(5, transactions.size());
	Assert.assertTrue(transactions.containsKey(null));

	Transaction txn = transactions.remove(null);

	Assert.assertEquals(4, transactions.size());
}

Example 3: The Key doesn’t exist in Map and it returns null.

@Test
public void remove3() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Transaction txn = transactions.remove(Long.valueOf(1L));
	Assert.assertNull(txn);
}

13. void clear()

clear() method is used to null out all the Key-Value mappings in HashMap. Once the clear() method returns, the HashMap’s size will be 0. 

@Test
public void clear() {
	Map<String, String> children = new HashMap<>();
	children.put("Jon", "Snow");
	children.put("Brandon", "Stark");
	children.put("Arya", "Stark");
	children.put("Sansa", "Stark");
	children.put("Rickon", "Stark");

	Assert.assertEquals(5, children.size());

	children.clear();

	// Every key-value pair is nulled out. 
	// But the internal table size remains the same.
	Assert.assertEquals(0, children.size());

}

14. Set<K> keySet()

Set<K> keySet() returns Set<K> keys. This method returns a view of all the keys. The view returned is backed by keys of the HashMap. So if the HashMap changes, this Set<K> will change too.

Example 1: Get the keys of this HashMap and print them all.

@Test
public void keySet1() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Set<Long> keySet = transactions.keySet();

	for (Long txnId : keySet) {
		System.out.println(txnId);
	}
}

Output:
3084133165970564918
7013460311305119709
3002995788216969326
6129339334756346255

Example 2: Get the keys of this HashMap and print them all. The Map contains null key.

@Test
public void keySet2() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	transactions.put(null, txnDataSet.get(0));
	Set<Long> keySet = transactions.keySet();

	for (Long txnId : keySet) {
		System.out.println(txnId);
	}
}

Output: 
3084133165970564918
7013460311305119709
3002995788216969326
6129339334756346255
null

15. Collection<V> values()

Collection<V> values() returns all the values from the HashMap. The values returned are backed by this HashMap, so any changes to HashMap will cause changes to Collection<V> too.

Example: Get all values from HashMap and print the amount of the Transaction.

@Test
public void values() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Collection<Transaction> txns = transactions.values();
		txns.stream()
			.forEach(txn -> System.out.println(
								txn.currency() + " " + txn.amount()));
}

Output:
AUD 16.00
CAD 11.00
USD 25.00
USD 20.00

16. Set<Map.Entry<K, V>> entrySet()

Set<Map.Entry<K, V>> entrySet() method is used to return Set of Entry. Entry is the inner interface in the Map interface which signifies the Key-Value pair.

The entry set returned is backed by this HashMap, so any changes to HashMap will cause changes to Set<Map.Entry<K, V>> too. 

Example: Print all the txnIds along with the amount of the transaction.

@Test
public void entrySet() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Set<Entry<Long, Transaction>> entrySet = transactions.entrySet();
	for (Entry<Long, Transaction> entry : entrySet) {
		Long txnId = entry.getKey();
		Transaction txn = entry.getValue();
		System.out.println(txnId + " " 
							+ txn.currency() 
							+ " " + txn.amount());
	}
}

Output:
6908070615736267361 AUD 16.00
6640384187622978960 CAD 11.00
3329744428125366233 USD 25.00
3971869604345832907 USD 20.00

17. int size()

size() method returns total number Key-Value mappings in the Map.

If the Map contains more than Integer.MAX_VALUE entries then, Integer.MAX_VALUE is returned. It would be a rare use case that the total number of entries exceeds Integer.MAX_VALUE.

@Test
public void size() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	Assert.assertEquals(4, transactions.size());
}

18. boolean isEmpty()

isEmpty() method returns true if there are any entries in Map. If there are no entries, it returns true, else returns false.

You can say that if size == 0 then isEmpty() will return true.

@Test
public void isEmpty() {
	Map<Long, Transaction> transactions = new HashMap<>();

	Assert.assertTrue(transactions.isEmpty());

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}

	Assert.assertFalse(transactions.isEmpty());
}

19. boolean equals(Object o)

equals(Object o) method is used to check equality of two different Maps. The equals method will return true if all entries in both the Maps are equal.

Example 1: We declare two Maps. One contains House Stark’s children and the other contains elders of House Stark. The entries are not same and the equals method returns false.

@Test
public void equals1() {
	Map<String, String> children = new HashMap<>();
	children.put("Brandon", "Stark");
	children.put("Arya", "Stark");
	children.put("Sansa", "Stark");
	children.put("Rickon", "Stark");

	Map<String, String> elders = new HashMap<>();
	elders.put("Eddard", "Stark");
	elders.put("Catlyn", "Stark");
	elders.put("Lyanna", "Stark");
	elders.put("Benjen", "Stark");

	Assert.assertFalse(children.equals(elders));
}

Example 2: We declare two Maps. One contains House Stark’s children and we create the other Map from this Map itself. All the entries are the same and the equals method returns true.

@Test
public void equals2() {
	Map<String, String> children = new HashMap<>();
	children.put("Brandon", "Stark");
	children.put("Arya", "Stark");
	children.put("Sansa", "Stark");
	children.put("Rickon", "Stark");

	Map<String, String> starkChildren = new HashMap<>(children);

	Assert.assertTrue(children.equals(starkChildren));
}

20. int hashCode()

hashCode() method computes the hashCode of all entries, adds them up and returns it.

hashCode() method returns 0 if Key-Value mapping is null.

Example 1: We provide 4 different entries to transactions HashMap. It returns a hashCode that is not 0. 

@Test
public void hashCode1() {
	Map<Long, Transaction> transactions = new HashMap<>();

	List<Transaction> txnDataSet = Transactions.getDataSet();

	for (Transaction txn : txnDataSet) {
		transactions.put(txn.transactionId(), txn);
	}
	// hashcode 333740093
	Assert.assertTrue(transactions.hashCode() != 0);
}

Example 2: We add Key-Value as null and null. The hashCode of this Map is 0 and hashCode of this Entry is also 0.

@Test
public void hashCode2() {
	Map<Long, Transaction> transactions = new HashMap<>();
	transactions.put(null, null);
	Assert.assertEquals(1, transactions.size());
	Assert.assertTrue(transactions.hashCode() == 0);
}

21. Java 8, 9 and 10 methods.

Click here to read about new methods added in Java 8, 9 and 10.

Entry interface has new methods, as of Java 8, for key and value comparison. Click here to read about it.

22. Conclusion

In this article, we saw several methods of HashMap class and how to use them with examples.

Leave a Reply

Your email address will not be published. Required fields are marked *