I am very much aware that there is a contract between equals() and hashCode() is basically: if equals() method is true, hashCode() mustreturn the same value, but vice versa not true.
Case-1: Only Hashcode is implemented, no equals method
public class Employee {
private String firstName;
private String lastName;
private int age;
public Employee() {
}
public Employee(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// No equals method implemented
@Override
public int hashCode() {
return 31;
}
public static void main(String[] args) {
Employee employee1 = new Employee("John", "Doe", 11);
Employee employee2 = new Employee("Jane", "Doe", 12);
Employee employee3 = new Employee("Joe", "Doe", 13);
Employee employee4 = new Employee("Joe", "Doe", 13);
Map<Employee, String> stringMap = new HashMap<>();
stringMap.put(employee1, "John");
stringMap.put(employee2, "Jane");
stringMap.put(employee3, "Joe");
stringMap.put(employee4, "Joe");
System.out.println(stringMap.size());
System.out.println(stringMap.get(employee4));
}
}
Output -
4
Joe
Case-2: Equals method implemented, but no hashcode
public class Employee {
private String firstName;
private String lastName;
private int age;
public Employee() {
}
public Employee(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(firstName, employee.firstName) && Objects.equals(lastName, employee.lastName);
}
public static void main(String[] args) {
Employee employee1 = new Employee("John", "Doe", 11);
Employee employee2 = new Employee("Jane", "Doe", 12);
Employee employee3 = new Employee("Joe", "Doe", 13);
Employee employee4 = new Employee("Joe", "Doe", 13);
Map<Employee, String> stringMap = new HashMap<>();
stringMap.put(employee1, "John");
stringMap.put(employee2, "Jane");
stringMap.put(employee3, "Joe");
stringMap.put(employee4, "Joe");
System.out.println(stringMap.size());
System.out.println(stringMap.get(employee4));
}
}
Output -
4
Joe
Why in both cases, its showing the same output? How the things are working internally?
If we properly implement the equals and hashcode method, then then we get the uniqueness among the collection (in this case, output - 3 Joe) and its advisable to use equals and hashcode together when operating on collection.
CodePudding user response:
Case-1: Returning same hashcode value is considered legal according to the hash code contract but it would not be very efficient. If this method is used, all objects will be stored in the same bucket i.e. bucket 31 and when you try to ensure whether the specific object is present in the collection, then it will always have to check the entire content of the collection. This creates performance issues.The best hash function will do a good job of distributing unequal instances into separate buckets.
Case-2: If you override only equals property and avoid hashcode, then There might be duplicates in the HashMap or HashSet. We write the equals method and expect{"abc", "ABC"} to be equals. However, when using a HashMap, they might appear in different buckets, thus the contains() method will not detect them each other.
public class Employee {
private String firstName;
private String lastName;
private int age;
public Employee() { }
public Employee(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(firstName, employee.firstName) && Objects.equals(lastName, employee.lastName);
}
public static void main(String[] args) {
Employee employee1 = new Employee("John", "Doe", 11);
Employee employee2 = new Employee("Jane", "Doe", 12);
Employee employee3 = new Employee("Joe", "Doe", 13);
Employee employee4 = new Employee("joe", "Doe", 13);
Map<Employee, String> stringMap = new HashMap<>();
stringMap.put(employee1, "John");
stringMap.put(employee2, "Jane");
stringMap.put(employee3, "Joe");
stringMap.put(employee4, "joe");
System.out.println(stringMap.size());
System.out.println(stringMap.get(employee4));
System.out.println(stringMap.containsKey(employee4));
System.out.println(stringMap.get(employee1).hashCode());
System.out.println(stringMap.get(employee2).hashCode());
System.out.println(stringMap.get(employee3).hashCode());
System.out.println(stringMap.get(employee4).hashCode());
}
}
Output -
4
joe
true
2314539
2301262
74656
105408
You can clearly see that Joe and joe is treated as different and appear in the different bucket locations.
CodePudding user response:
First case where we return a constant value 31. and if you do not override then by default equal method check for the same reference instead of value.
There are majorly 2 issues with first approach.
if you have identical clone object still you cannot search because you are using different instance and internally hash map uses equal method to determine your matching key and its value.
same hashcode put all your object in same bucket of hashes which demotivates the power of hashing. and all the operation took O(n) time complexity instead of O(1)
Case 2. when you did not override hashcode but override equals.
In this case when you update value with identical clone key. It will create unexpected result because two same object have different hashcode but return equal.
All in all hash-map creates buckets using hashcode. more diverse hashcode makes Hashmap faster in term of time complexity.
if two keys have same Hashcode then it store under the same bucket of same hashcode. Hashmap still relies on equal method to verify the exact key.
So your approach have some downside but it does not mean it returns wrong results.