Hashmap with class objects [duplicate] - java

This question already has answers here:
Understanding the workings of equals and hashCode in a HashMap
(9 answers)
Closed 2 years ago.
I have a Test class and its objects with same value (101,"abc") is created twice. I am inserting these two object in Hashmap as a Key. I want to understand the internal functioning as to why I am getting size as two of a map when both of my keys are same, it should probably overwrite ?
import java.util.HashMap;
import java.util.Map;
public class Test{
int id;
String name;
public static void main(String[] args) {
Test e1 = new Test(101,"abc");
Test e2 = new Test(101,"abc");
//Test e3 = new Test(101,"abc");
Map<Test, String> map = new HashMap<>();
map.put(e1, "XYZ");
map.put(e2, "CDF");
String value = map.get(e2);
System.out.println( "VALUE : "+value);
}
public Test(int id, String name) {
this.id = id;
this.name=name;
}}

Test e1 = new Test(101,"abc");
Test e2 = new Test(101,"abc");
Creates 2 different objects of type Test. This means 2 different memory space has been allocated for e1 & e2.
Now lets understand how does map( say Hash Map) identifies the uniqueness of key( in much simpler words, how does the map knows that the key you are trying to insert is already present or not). Answer is, map calls the hashcode() & equals() method to compare the keys present in map with the one you are trying to insert.
As we already know all classes have a default parent class Object . And the Test class is not having an implementation of hashcode() & equals(); so when map tried calling them, the object class method were called.
As Object's class equals returns true only when both the objects in comparison refers to the same object reference & so does the hashcode() and here e1 & e2 are clearly different objects. So you got two entries.
Solution is to have an override the equals() & hashcode() as suggested by #Kapil above.

There will be two objects in the hashmap unless you override hashcode() and equals() methods in your Test class. If you override these two methods on the id property of Test class then the two keys with the same id would be treated as duplicate and you will see the expected behaviour.
Example below states that you want to make two object equals if their ids are equal -
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Test other = (Test) obj;
if (id != other.id)
return false;
return true;
}

Related

Why does HashMap override the existing object when I am returing different hash codes for the same object?

import java.util.HashMap;
import java.util.Map;
class Geek
{
public String name;
public int id;
Geek(String name, int id)
{
this.name = name;
this.id = id;
}
#Override
public boolean equals(Object obj)
{
// checking if both the object references are
// referring to the same object.
if(this == obj)
return true;
// it checks if the argument is of the
// type Geek by comparing the classes
// of the passed argument and this object.
// if(!(obj instanceof Geek)) return false; ---> avoid.
if(obj == null || obj.getClass()!= this.getClass())
return false;
// type casting of the argument.
Geek geek = (Geek) obj;
// comparing the state of argument with
// the state of 'this' Object.
System.out.println("equals method ....."+(geek.name == this.name && geek.id == this.id));
return (geek.name == this.name && geek.id == this.id);
}
int counter = 0;
#Override
public int hashCode()
{
// We are returning the Geek_id
// as a hashcode value.
// we can also return some
// other calculated value or may
// be memory address of the
// Object on which it is invoked.
// it depends on how you implement
// hashCode() method.
++counter;
System.out.println("counter ::>>> "+counter);
return counter;
}
Driver code:
public static void main (String[] args)
{
Map<Geek, Integer> map = new HashMap<>();
// creating the Objects of Geek class.
Geek g1 = new Geek("aa", 1);
Geek g2 = new Geek("aa", 1);
map.put(g1, g1.id);
map.put(g2, g2.id);
map.forEach((k,v) -> {
System.out.println("key = "+k + "\n value = "+v);
});
/* else
System.out.println("Both Objects are not equal. "); */
}
Here, I am overriding the hashCode() method but still the map contains only one object which is g2. Why didn't the HashMap store two objects, given that my hashcode returns a different integer every time?
Even though my equals() method returns true for the same object, why is the HashMap not storing two objects? Can someone please guide me in this regard?
Your counter variable is an instance variable, so it's initialized to 0 for each Geek instance. Therefore, both g1 and g2 have the same hashCode() of 1 when you put them in the Map, and are considered identical by the HashMap, since they are equal to each other based on your equals implementation.
If you change counter to be static, you will get different hashCode() for the 2 instances of Geek, and they would be stored in separate map entries.
That said, your hashCode() implementation is very bad. If you call hashCode() for the same instance multiple times, you'll get a different result each time! This means that if you attempt to put g1 twice in the Map, it will probably put it twice, since the second put will see a different hashCode(), and will therefore search for the key in a different bucket.
hashCode() function must not change when you call it on the same object instance multiple times. You can't generate a new value each time you call it, right now you are doing it by incrementing counter.
As per Object.hashCode() javadoc:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

Who calls the equals method of the class while putting the elements into the HashMap?

I am new to Java (very new).
I am trying to understand HashMap and the equals method of a class and how it overrides the duplicates.
Please see following code:
public class Student {
Integer StudentId;
String Name;
String City;
public Student(Integer studentId, String name, String city) {
super();
StudentId = studentId;
Name = name;
City = city;
}
public Integer getStudentId() {
return StudentId;
}
public String getName() {
return Name;
}
public String getCity() {
return City;
}
#Override
public int hashCode() {
System.out.println("haschode is called for " + this);
final int prime = 31;
int result = 1;
result = prime * result + ((StudentId == null) ? 0 : StudentId.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
System.out.println("equals is called for " + this);
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (StudentId == null) {
if (other.StudentId != null)
return false;
} else if (!StudentId.equals(other.StudentId))
return false;
return true;
}
#Override
public String toString() {
return "\n Student [StudentId=" + StudentId + ", Name=" + Name + ", City=" + City + "] \n";
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<Student, String> myMap = new HashMap<Student, String>();
myMap.put(new Student(1, "andy", "p"), "Great"); //Line 1
myMap.put(new Student(2, "sachin", "m"), "Better");
myMap.put(new Student(3, "dev", "s"), "Good");
myMap.put(new Student(1, "andy", "p"), "Excellent"); // Line 4
System.out.println(myMap);
}
}
Now, the code written in main() calls the equals method only when I write the code to put the same key again i.e. "Line 4" (see my code indentation).
Why is the equals method not called for "Line 2" and "Line 3"??
It should call equals for every put line .... correct?
I am missing some understanding here and am left with questions:
(1) Why is every put not calling the equals method to check the equality of class members?
(2) Who triggers the call of the Student class equals method?
It should call equals for every put line .... correct ?
No. A HashMap will call equals only after it encounters a hash collision between an existing key and the one given in put.
Rephrased, it calls hashCode first to determine which "hash bucket" to put the key into, and if there are already keys inside the target bucket, it then uses equals to compare the keys in the bucket for equality.
Since the value of Student.hashCode() is based on ID alone, during insertion, the map only needs to call equals when it encounters a Student key with the same ID as what is being inserted. If no existing keys have the same hashCode as the one being inserted, there is no need to call equals.
This makes HashMap very efficient during insertion. This is also why there is a contract between hashCode and equals: If two objects are equal as defined by equals, they must also have the same hashCode (but not necessarily vice-versa).
equals() is not called if the hashCode() result is different. It's only the same for Line 1 and Line 4 (same student Id of 1), so equals() is called for that.
Note that hashCode() may be the same for two objects that aren't equals(), but two equals() objects must never have a different hashCode():
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
So the initially different hash code is enough to not call equals() afterwards.
The whole purpose of a hash-based map is to operate on hash values (for efficiency that is).
The Map first and foremost cares about different hash values. Thus, as long as any "incoming" key has an (so far) unknown hash, equality doesn't matter.
Only when you run into a conflicting hash, then it matters whether that incoming key is actually a different key, or the same key. In the first case, you add a new key/value pair to the map, in the second case, you update an already stored key with a potential new value!
Therefore calling equals() only happens for situations where the Map implementation has to decide whether two keys that have the same hash are equal, or not.
If hash code differs, then there is no case for calling equals. Look at the code for HashMap(). If the hash is the same, then equals is called.
As you can see while running your code, hashcode() is called for every .put() call.
Basicly hashcode is called for every put() operation, if it is unique then a new element can be placed in the map - as one of the conditions for the hashcode() says, that different hashcodes always represent different objects. However, different object don't always have different hashcodes. Because of that, if the hashcodes are the same for two objects, hashmap have to check object equality with equals().
If you want to watch how the hashCode and equals methods work for selected values,
create the following map:
Map<Key, Value> map = new HashMap<>();
Then create instances of the following classes and use them to populate the Map. I recommend using a String as the object of both classes since I used its equals method in both.
Notice that you supply the hashCode to be returned. This allows it to be the same or different so you can see how the map behaves in different situations.
class Value {
private Object obj;
private int hashCode;
public Value(Object obj, int hashCode) {
this.obj = obj;
this.hashCode = hashCode;
}
public int hashCode() {
System.out.println("Value: hashCode is called - " + hashCode);
return hashCode;
}
public boolean equals(Object o) {
System.out.println("Value: equals is called - " + obj);
return obj.equals(o);
}
public String toString() {
return "Value: obj = " + obj + ", hashCode = " + hashCode;
}
}
class Key {
private Object obj;
private int hashCode;
public Key(Object obj, int hashCode) {
this.obj = obj;
this.hashCode = hashCode;
}
public int hashCode() {
System.out.println("Key: hashCode is called - " + hashCode);
return hashCode;
}
public boolean equals(Object o) {
System.out.println("Key: equals is called - " + obj);
return obj.equals(o);
}
public String toString() {
return "Key: obj = " + obj + ", hashCode = " + hashCode;
}
}
You can read the source code of HashMap.java in JDK 1.7.
Than you will understand the questions you asked.
Other answers are more helpful after you have reading the source code of HashMap.

Hashtable get() operation not working as per documentation

According to the official documentation for the Java Hashtable class (https://docs.oracle.com/javase/7/docs/api/java/util/Hashtable.html), the get() opperation will return one of it's recorded values, if said value has a key that returns true when the parameter is fed into that key's equals() opperation.
So, in theory, the following code should return "Hello!" for both of the Hashtable's get() queries:
public static class Coordinates implements Serializable {
private int ex;
private int why;
public Coordinates(int xCo, int yCo) {
ex = xCo;
why = yCo;
}
public final int x() {
return ex;
}
public final int y() {
return why;
}
public boolean equals(Object o) {
if(o == null) {
return false;
} else if(o instanceof Coordinates) {
Coordinates c = (Coordinates) o;
return this.x() == c.x() && this.y() == c.y();
} else {
return false;
}
}
}
Hashtable<Coordinates, String> testTable = new Hashtable<Coordinates, String>();
Coordinates testKey = new Coordinates(3, 1);
testTable.put(testKey, "Hello!");
testTable.get(testKey); //This will return the "Hello" String as expected.
testTable.get(new Coordinates(3, 1)); //This will only return a null value.
However, get() doesn't work as it's supposed to. It seems to only work if you litterally feed it the exact same object as whatever was the original key.
Is there any way to correct this and get the Hashtable to function the way it's described in the documentation? Do I need to make any adjustments to the custom equals() opperation in the Coordinates class?
To be able to store and retrieve objects from hash-based collections you should implement/oeverride the equals() as well as hashCode() methods of the Object class. In your case, you have overridden the equals() and left the hashCode() method to its default implementation inherited from the Object.
Here is the general contract of the hashCode() method you must consider while implementing it:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
And here is an example implementation that is generated from my IDE (as alread mentioned by #Peter in the comment area) which you can modify to suit your requirements:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ex;
result = prime * result + why;
return result;
}

Same hashCode but two different entries in HashMap [duplicate]

This question already has answers here:
Java HashMap return value not confirming with my understanding of equals and hashcode
(4 answers)
Closed 8 years ago.
What I know is: While inserting elements in the HashMap, Java checks value of hashCode and inserts that element in inside the HashMap and while retrieving the object from HashMap, Java checks the value of HashCode and retrieved the object that has the value generated from that HashCode. Is this correct?
I created a modal to override default implementation of HashCode. Every time that modal is called, it gives back a same value. So, if we add that modal again and again, why entries in HashMap are increasing?
Here is my code:
Modal:
public class MyModal {
int empId;
String empName;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyModal myModal = (MyModal) o;
if (empId != myModal.empId) return false;
if (empName != null ? !empName.equals(myModal.empName) : myModal.empName != null) return false;
return true;
}
#Override
public int hashCode() {
return 1;
}
public MyModal(int empId, String empName) {
this.empId = empId;
this.empName = empName;
}
}
public class TestHashCode {
public static void main(String[] args) {
HashMap<MyModal, Integer> hashMap = new HashMap<>();
MyModal modal1 = new MyModal(1, "a");
MyModal modal2 = new MyModal(2, "b");
hashMap.put(modal1, 1);
hashMap.put(modal2, 2);
System.out.println("Size is" + hashMap.size());
System.out.println(modal1.hashCode() + " "+modal2.hashCode());
}
}
Output:
Size is2
1 1
Hash collisions aren't the only limiting factor in the map construction, however returning a constant 1 is a "worst" case Map and it behaves like a LinkedList (every element is in one bucket). From the Object.hashCode() Javadoc
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
Both HashCode and Equals need to be implemented. Hash code is used to narrow the search in equals need to be implemented to define equality. Two unequal objects can have same hashcode but does not mean they are equal.
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode().

Search an ArrayList using an object [duplicate]

This question already has answers here:
Search an ArrayList for a particular Object
(6 answers)
Closed 9 years ago.
I have an ArrayList that is defined as follows:
ArrayList<Doctor> arr = new ArrayList<Doctor>();
The Doctor class contains the following:
String name;
String gender;
String speciality;
In the arr arraylist i have added 100 Doctor objects.
Now i need to search the arr ArrayList to see if a particular doctor object is present.
I tried the following approach;
boolean contains = maarrp.containsKey(doc.name);
But, i don't want to compare using keys or an element of the Doctor object. Instead i want to compare the whole doctor object. How can i do this?
You need to implement the equals() and hashcode() methods in you Doctor Object then Search against ArrayList like below.
ArrayList<Doctor> arr = new ArrayList<Doctor>();
arr.add(new Doctor());
arr.add(new Doctor());
arr.add(new Doctor());
arr.add(new Doctor());
if(arr.contains(doc){
}
Create your Doctor class like below
class Doctor{
Long id;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Doctor other = (Doctor) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
Override equals() and hashcode() methods in Doctor class.
your class should be something like :
class Doctor
{
String name;
String gender;
String speciality;
public boolean equals()
{
//some logic on which you want to say
//two doctors are same.
return true;
}
public int hashCode()
{
return 0;
}
}
for hashCode you must follow these rules :
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
Note : hashCode must not always return 0 :)
Override equals() and hashcode() methods in Doctor Class
/*add a unique id field in doctor class to make each doctor unique.*/
String docUniqueId;
String name;
String gender;
String speciality;
#Override
public boolean equals(Object ob) {
if (!ob instanceof Doctor ) return false;
Doctor that = (Doctor )ob;
return this.docUniqueId== that.docUniqueId;
}
#Override
public int hashCode() {
return docUniqueId;
}
You could implement the equals() and hashcode() methods of your Doctor class. These methods are called when the contains() method of the ArrayList object is called.
To simplify this work, you can eventually use the commons-lang library which provides:
EqualsBuilder (http://commons.apache.org/proper/commons-lang/apidocs/index.html?org/apache/commons/lang3/builder/ToStringBuilder.html)
and HashCodeBuilder (http://commons.apache.org/proper/commons-lang/apidocs/index.html?org/apache/commons/lang3/builder/HashCodeBuilder.html)

Categories