Can we use an object as a key for a hashmap in java? This is a very popular interview question indeed. It is asked immediately after “How HashMap works?”. Lets make a reasoning around user defined object as key in hashmap in java.
1. The contract between hashCode() and equals()
The very basic need for designing a good key is that “we should be able to retrieve the value object back from the map without failure“, otherwise no matter how fancy data structure you build, it will be of no use. To decide that we have created a good key, we MUST know that “how HashMap works?”. I will leave, how hashmap works, part on you to read from linked post, but in summary it works on principle of Hashing.
Key’s hash code is used primarily in conjunction to its equals()
method, for putting a key in map and then getting it back from map. So, our only focus point is these two methods. So if hash code of key object changes after we have put a key value pair in map, then its almost impossible to fetch the value object back from map. It is a case of memory leak.
On runtime, JVM computes hash code for each object and provide it on demand. When we modify an object’s state, JVM set a flag that object is modified and hash code must be AGAIN computed. So, next time you call object’s hashCode()
method, JVM recalculate the hash code for that object.
2. Make HashMap key object immutable
For above basic reasoning, key objects are suggested to be IMMUTABLE. Immutability allows you to get same hash code every time, for a key object. So it actually solves most of the problems in one go. Also, this class must honor the hashCode() and equals() methods contract.
This is the main reason why immutable classes like String
, Integer
or other wrapper classes are a good key object candidate. and it is the answer to question why string is popular hashmap key in java?
But remember that immutability is recommended and not mandatory. If you want to make a mutable object as key in hashmap, then you have to make sure that state change for key object does not change the hash code of object. This can be done by overriding the hashCode()
method. But, you must make sure you are honoring the contract with equals() also.
3. HashMap custom key object example
An example is always better for demonstration, right? Then lets have one.
In this example, I have created an Account
class with only two fields for simplicity. I have overridden the hashcode and equals method such that it uses only account number to verify the uniqueness of Account object. All other possible attributes of Account
class can be changed on runtime.
package com.howtodoinjava.demo.map; public class Account { private int accountNumber; private String holderName; public Account(int accountNumber) { this.accountNumber = accountNumber; } public String getHolderName() { return holderName; } public void setHolderName(String holderName) { this.holderName = holderName; } public int getAccountNumber() { return accountNumber; } //Depends only on account number @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + accountNumber; return result; } //Compare only account numbers @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Account other = (Account) obj; if (accountNumber != other.accountNumber) return false; return true; } }
Will this cause any undesired behavior???
NO, it will not. The reason is that Account
class’s implementation honor the contract that “Equal objects must produce the same hash code as long as they are equal, however unequal objects need not produce distinct hash codes.” i.e.
- Whenever a.equals(b) is true, then a.hashCode() must be same as b.hashCode().
- Whenever a.equals(b) is false, then a.hashCode() may/may not be same as b.hashCode().
4. Test the HashMap custom key object
Lets test our Account
class for above analysis.
package com.howtodoinjava.demo.map; import java.util.HashMap; public class TestMutableKey { public static void main(String[] args) { //Create a HashMap with mutable key HashMap<Account, String> map = new HashMap<Account, String>(); //Create key 1 Account a1 = new Account(1); a1.setHolderName("A_ONE"); //Create key 2 Account a2 = new Account(2); a2.setHolderName("A_TWO"); //Put mutable key and value in map map.put(a1, a1.getHolderName()); map.put(a2, a2.getHolderName()); //Change the keys state so hash map should be calculated again a1.setHolderName("Defaulter"); a2.setHolderName("Bankrupt"); //Success !! We are able to get back the values System.out.println(map.get(a1)); //Prints A_ONE System.out.println(map.get(a2)); //Prints A_TWO //Try with newly created key with same account number Account a3 = new Account(1); a3.setHolderName("A_THREE"); //Success !! We are still able to get back the value for account number 1 System.out.println(map.get(a3)); //Prints A_ONE } }
Program Output.
A_ONE A_TWO A_ONE
This is my understanding on designing custom key object for HashMap. If you disagree or feel otherwise, please drop me a comment.
Happy Learning !!
References:
kjp
how to do hashmap value object (is my approach correct ), I have written this code, how to get the city details,
My csv file contains these data,
city,city_ascii,lat,lng,country,iso2,iso3,admin_name,capital,population,id
Malishevë,Malisheve,42.4822,20.7458,Kosovo,XK,XKS,Malishevë,admin,,1901597212
Prizren,Prizren,42.2139,20.7397,Kosovo,XK,XKS,Prizren,admin,,1901360309
Zubin Potok,Zubin Potok,42.9144,20.6897,Kosovo,XK,XKS,Zubin Potok,admin,,1901608808
Kamenicë,Kamenice,42.5781,21.5803,Kosovo,XK,XKS,Kamenicë,admin,,1901851592
Viti,Viti,42.3214,21.3583,Kosovo,XK,XKS,Viti,admin,,1901328795
Shtërpcë,Shterpce,42.2394,21.0272,Kosovo,XK,XKS,Shtërpcë,admin,,1901828239
________________________
___________________________
arun singh
pretty deep and difficult to understand in one go.
but great article, thanks for the information.
bala
Save my day! Thanks Lokesh!
Divya
How do you access values of a hashmap in a class from a different class in Java? please help!
Kd
Awesome..felling that I m learning hashmap now..
Subodh
Hi Lokesh,
One more point to add to the answer is not using the transient variables in hashCode()
If the object is serailized, it would get default value for transient variable instead of instance value , making it impossible to locate
jyotsna
Hi Lokesh,
could you plz explain me below doubts ?
if HashMap key is a String object/user defined class obj then how the hashcode generates ?
HashMap hm= new HashMap();
hm.put(21, 121);
normally hashcode calulates : key %capacity = 21%4= 1 // suppose my Initial capacity is 4 and it will tore @ index position 1
same way want to know how it calculates for a String and userdefined class as a key ..
Sandeep
Interview questions:
what is the output
System.out.println(employeeObjectMap.size());
System.out.println(employeeObjectMap.get(employee3));
RAJIV SARKAR
MapSize :1 as every time it generates same hashcode so same bucket is use for out opeation.
As equals always returns false it wont be able to get the object from map.
RAJIV SARKAR
MapSize will be number of put operations you do as every time it generates same hashcode so same bucket is use for out opeation but since equals method returns false every time it stores in different entry of map.
As equals always returns false it wont be able to get the object from map.
RAJIV SARKAR
please ignore my first answer
GURDEEP SINGH SAINI
is equals methods compare each key on the same bucket and if keys are equal then replace the values to the recent keys value?
please explain how equal method works in hashmap?
siddharhta
In Hash map equals method works on keys. Generally Hash code is may same or may not same for two different keys. if hash code is same and and the index is same but keys are different In this case hash map create a linked list and stores the values at the next node of present key value pair
Arun Vadivel
This article is very informative.. Thank you very much..
Sangram
above hashcode method :
final int prime = 31;
int result = 1;
result = prime * result + accountNumber;
return result;
is same as :
return 31 * accountNumber;
Any reason why you wrote those 4 lines?
Lokesh Gupta
No. You are right that it could be one line code also. But if you have multiple fields in hashCode() calculation then It will be easy to change them method as below :
Ashish Thakur
To avoid any unforced error as reassigning value to accountNumber, It’s better if we make the field “accountNumber” final.
Ashish Thakur
To avoid any unforced error as reassigning accountNumber a new value,It would be better if you make “accountNumber” final
dob
i think it is important that neither equals nor hashcode should change when changing the state of object. In your example, if I have hashcode on the basis of account number and equals on the basis of account number and holder name, then neither property can be changed if you a reliable key is required for map.
Lokesh Gupta
Absolutely true. Agree “that neither equals nor hashcode should change when changing the state of object.”
Priyanka
can you please elaborate?.. Dint get it..
Lokesh Gupta
If hashcode and equals will change after inserting the ‘key’ object in map, you will not get same ‘value’ object from map.. and even you may loose the value object all together thus causing memory leak.
Pravin
I had run your sample programme but it will not correctly run
Lokesh Gupta
Any particular problem you see?
samba
Very good one.Thanks Lokesh
DreamUth
Nice post.
In #33, you are setting for value for a1 but I guess you might be interested in setting value for a3.
Assume that, I have included holdername in equals method and I put the a3 in the same map. Then the above code will return “A_THREE” because after matching the hashcode, it will use the equals to identify the correct key. Please correct me if I am not
Lokesh Gupta
Typo corrected. Yes, you are right.
Suraj
Hi.. I included holdername in equals method and then put a3 in the same map . I got null as output for a3 as key.
If i include holdername in hash code as well then i get null for all the three keys. Can you please mention as to how will I get “A_THREE “as value for a3 as key.
Suraj
apologies… i made a mistake. yes u are right.
Lokesh Gupta
Never mind.
Suraj
Hi Lokesh, Can u help me in understanding the output following program. Why the output is different in the below two cases?
Lokesh Gupta
Just to help others reading this: Output of above program is –
World
Hello
In your first part of program, you created an instance of
Test
and set it’s value to “Hello”. After inserting this instance in map, you used the same reference to change the value to “World”. So value got changed.In second part of program, you created instance of
StringBuffer
and set value to “Hello”. BUT, now you created another instance ofStringBuffer
and assigned the reference to variablesb
. Please note that after changing the reference,sb
does not point to firstStringBuffer
instance, rather it points to second instance. So any operation you does usingsb
now, does not affect the instance inside map. So when you fetch it from map, it’s unchanged.If you really want to change the value, then do not assign the reference of new
StringBuffer
tosb
, rather simply usesb.append()
method.Now output will be:
World
HelloWorld
Boutique
Hey well explained, but I am not clear about immutability. Since you told at the start how to create immutable class. I dont see it you above example. Am I missing anything here ?
Lokesh Gupta
Please read the linked post: https://howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/
Ajay
Is the last println line correct in TestMutableKey??
we have not added a3 obj into map, so how can we retrieve it?
Ajmal CH
Hi Ajay,
As far I I understand, both a1 and a3 would produce same hascode and also equals method wouldl return true since both account numbers are same. That are the only two things HashMap will check before returning a value object from the map.
Nate
What if I would like both values in the account class to be immutable? Like the account number and the holder name would have to be equal for the objects to be equal? How does this change the hashcode and equals methods?
Shailesh
Nice Article
sagar
1.) when a hashcode value is calculated , this value happens to be some memory address on the heap. What is the guarantee OR how hashMap ensures that the hashcode that will be calculated will be a free mem area(the same mem area is not being used by some other program)
2.) Although internally objects will be stored in a transient Entry[]. Is it that this array which is a datastructure with contiguous mem locations will already claim its space on the heap, once declared and then the bucket allocation happens from within this transient array.
Kindly help on the above 2 questions
Lokesh Gupta
1) NO, hashcode is not same as memory address. Rather it’s kind of representation of memory address. For default hashCode() method, JVM derives the value from the value of the reference to the object. So, first object is created in memory and then hashcode is calculated.
2) Yes you are right.
Shaon
Can i do the same hascode implementation with String, as u did with integer value(Acc no)? If so how?what should be the code inside hashcode?
Lokesh Gupta
You can use StringVar.hashCode() in your hashCode implementation.
Anurag
Very good post. It clearly explains
Maddy
Most important thing to know about HashMap is it’s data structures and algorithms used to write this class. As name of class(HashMap) is indicating that its works on hashing mechanism. This class basically uses two data structures, one is Array and other is Linked-List. HashMap internally create Array of Entry type objects. Entry is an inner class used by HashMap to stores Key and Value type’s objects. To place an Entry object in array, we need an index at which that object can store in array. This index is generated by hash code of key object provide by user. Hash code of key object can get by hashCode() method of key object. After knowing index, Entry object can place in array. These array indexes are known as bucket. Now if you want to retrieve any object from HashMap, you need to provide key for that. This is key object gives hash code, this hash code generates an index and now at this index you have your object.
If you want to read more about HashMap [Click Here]
amit
Can u explain more why it becomes null if we don’t override hashcode and equals
Lokesh Gupta
In this case, Account a3 = new Account(1); creates an object whose hashcode will be different from a1. We have not put a3 in map, so it’s not available to matched with a1.
If we override hashcode and equals then a3 is matched with a1 due to same account number, and a1 is returned.
Aruna
Hi, I have not implemented hashcode() and equals() methods and the output is:
A_ONE
A_TWO
null
I have changed the accountNumber field to String, still get the same output
A_ONE
A_TWO
null
String being immutable, should the output be different?
Lokesh Gupta
No, both account objects are separate.
Arvind Prabhakar
Nice article .I really enjoyed all your articles.I have one request, could you please explain ThreadLocal .I tried understanding but not able to implement it practically. It will be very helpful.
Lokesh Gupta
I will write a post soon. By the time, you would like to read a practical example of Thread local: https://howtodoinjava.com/resteasy/share-context-data-with-jax-rs-resteasyproviderfactory/
Jacob Zimmerman
This is only acceptable if you are okay with the mutable parts of the class being unimportant for seeing if two instances of the class are “equal”. Many people might be thrown off by the idea of them changing something and seeing that it is still considered equal to something that it was equal to before. This is not neccessarily a bad thing, but you should probably state in the documentation that certain parts aren’t factored into its equality.
Lokesh Gupta
Fair enough.
patel
I get the same value even if I don’t override hashcode or equals? WHY
Lokesh Gupta
If you do not override the hashcode and equals function, output will be:
A_ONE
A_TWO
null
Which is different from above program.
Anuj
Hi Lokesh,
if in this case as we are not overriding the hashcode and equal method,and we are changing object state i.e.(mutable key) then how its is fetching the values A_ONE
A_TWO ?
Lokesh Gupta
That’s before modifying the keys. After modifying, it’s null.
Anuj
here account a1 always returns same hashcode even after setting the name.
Is that mean its not necessary that hashcode will change incase we are changing state of mutable class (Provided we are not override the method)
aavi
YOU ROCK MAN!
sandy
Very clearly explain.
aditya kumar
Best article i ever read on this topic…..
Nitin Madnani
Nice article man, keep up the good work !!!
Malli
Hi Lokesh,
Can you can explain hashmap get(), with example , i tried so many time times but i have some little bit confusion,
Lokesh Gupta
Have you gone through how hashmap works?