hashCode and Equals doesn't work properly in Map keys - java

I have Employee class and want to use this class as a key in HashMap and value of that map will be a string. To achieve this, however, I have overridden the equals and hashcode methods in Employee class itself. I have another class to test that whether Employee class key will work correctly or not. After testing, I found that map is storing duplicate keys. Please find below source code and output:
Employee.java
public class Employee {
private int empId;
private String empName;
private int empAge;
Employee(){}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public int getEmpAge() {
return empAge;
}
public void setEmpAge(int empAge) {
this.empAge = empAge;
}
#Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(!(obj instanceof Employee)) return false;
Employee e = (Employee)obj;
return e.empId == this.empId;
}
#Override
public int hashCode() {
return this.empId;
}
#Override
public String toString() {
// TODO Auto-generated method stub
return "Id : "+empId+" Name : "+empName+" Age : "+empAge;
}
}
TestEmployee.java
import java.util.HashMap;
import java.util.Map;
public class TestEmployee {
/**
* #param args
*/
public static void main(String[] args) {
Map<Employee, String> empMap = new HashMap<Employee, String>();
Employee emp1 = new Employee();
emp1.setEmpId(10);
emp1.setEmpName("A");
emp1.setEmpAge(20);
Employee emp2 = new Employee();
emp2.setEmpId(20);
emp2.setEmpName("B");
emp2.setEmpAge(21);
empMap.put(emp1, "1");
empMap.put(emp2, "2");
System.out.println(empMap);
emp1.setEmpId(20);
System.out.println(" emp1.equals(emp2) : "+emp1.equals(emp2));
System.out.println(" emp1.hashCode() : "+emp1.hashCode()+" emp2.hashCode() : "+emp2.hashCode());
System.out.println(empMap);
}
}
Output:
{Id : 20 Name : B Age : 21=2, Id : 10 Name : A Age : 20=1}
emp1.equals(emp2) : true
emp1.hashCode() : 20 emp2.hashCode() : 20
{Id : 20 Name : B Age : 21=2, Id : 20 Name : A Age : 20=1}
Please let me know how can I use unique Employee in map key - uniqueness will decide based on employee ID.

Do not update the value that is used for hashcode while the object is in a hashmap. The hashcode will be used to decide where to put the object. If you update it afterwards, this update is not "picked up" by the hashmap so at best you have duplicates, at worst you can't retrieve the value connected to the updated key anymore.
UPDATE
Please check the javadoc for more information but high level the HashMap chooses a bucket to put the value in based on the hashcode of the key. If you try to retrieve the value with the key, it will look at the hashcode of the key, determine which bucket it should be in, then check that bucket and retrieve the value.
However if between the put() and the get() the hashcode of the key changes, the hashmap might put it in bucket "A" and try to get it in bucket "B".

In your equals method instead of using '==' reference comparison use equals method
as '==' comparison checks for reference equality instead of value comparison.
Like
return e.empId.equals(this.empId);
Java Integer class properly overrides equals and it will give you correct result.

Related

Add unique elements in HashSet based on attributes of framework provided non-editable object classes

I am trying to generate a HashSet containing unique Employee instances. Uniqueness should be established based on the object properties.
The problem is that I end up with having duplicates.
Note that Employee class is provided by a framework, and it's not possible to provide custom implementations for equals() and hashCode().
Employee class:
public class Employee {
private long employeeId;
private String name;
// getters, setters
#Override
public String toString() {
return "Employee{" +
"employeeId=" + employeeId +
", name='" + name + '\'' +
'}';
}
}
Map<String, Set<Employee>> ackErrorMap = new HashMap<>();
Employee emp = new Employee(1,"bon");
Employee emp2 = new Employee(1,"bon");
ackErrorMap.computeIfAbsent("key1",
x -> new HashSet<>()).add(emp);
ackErrorMap.computeIfAbsent("key1",
x -> new HashSet<>()).add(emp2);
This would result in a Set mapped to the Key "key1" containing both emp and emp2, although object attributes are same.
Is there any way to discard Employee with the same name? Or would it better to use ArrayList?
Possible example using Arraylist
ackErrorMap.computeIfAbsent("key1",
x -> new ArrayList<>()).add(emp);
You can override the equals and hashCode methods in the Employee class. The equals method should return true if two objects are considered equal, and the hashCode method should return the same value for two objects that are considered equal.
class Employee {
private int id;
private String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id &&
Objects.equals(name, employee.name);
}
#Override
public int hashCode() {
return Objects.hash(id, name);
}
}
With these changes, when you add emp and emp2 to the HashSet, only one of them will be added, because they will be considered equal based on the equals method.
You can create a custom type wrapping the class coming from the framework and implement the equals/hashCode contract according to your requirements.
That's how such wrapper might look like (for the purpose of conciseness I'm using a Java 16 record, but it can be implemented as a class as well).
public record EmployeeWrapper(Employee employee) {
#Override
public boolean equals(Object o) {
return o instanceof EmployeeWrapper other
&& employee.getName().equals(other.employee.getName());
}
#Override
public int hashCode() {
return Objects.hash(employee.getName());
}
}
And you can use with a Map of type Map<String,Set<EmployeeWrapper>> to ensure uniqueness of the Employee based on the name property.
I would also advise to make one step further and encapsulate the Map into a class which would expose the methods covering all scenarios of interaction with the Map (like add new entry, get employees by key, etc.), so that your client would not dial wrappers, but only with employees and wrapping and unwrapping would happen within the enclosing class.
Here's how it might be implemented:
public class AcrErrors {
private Map<String, Set<EmployeeWrapper>> ackErrorMap = new HashMap<>();
public void addEmployee(String key, Employee employee) {
EmployeeWrapper wrapper = new EmployeeWrapper(employee);
ackErrorMap
.computeIfAbsent(key, x -> new HashSet<>())
.add(wrapper);
}
public List<Employee> getEmployees(String key) {
return ackErrorMap.getOrDefault(key, Collections.emptySet()).stream()
.map(EmployeeWrapper::employee)
.toList();
}
// other methods
}
You need to override equals() and hashCode() inside Employee class.
Or you can use lombok’s #EqualsAndHashCode annotation in your Employee class.

remove data from one list with another list data function not working properly

public static void getIndividualsList() {
List<Employee> individualList = new ArrayList<>();
Employee individualDtls = new Employee();
individualDtls.setDependentId(0);
individualDtls.setDateOfBirth("06/06/1998");
individualDtls.setEeId(1L);
individualDtls.setFullName("MICHAEL K HERNANDEZ");
individualDtls.setCovered(false);
individualDtls.setDependentType("Self");
individualList.add(individualDtls);
List<Employee> individualList1 = new ArrayList<>();
Employee individualDtls1 = new Employee();
individualDtls1.setDependentId(0);
individualDtls1.setDateOfBirth("06/06/1998");
individualDtls1.setEeId(1L);
individualDtls1.setFullName("MICHAEL K HERNANDEZ");
individualDtls1.setCovered(false);
individualDtls1.setDependentType("Self");
individualList1.add(individualDtls1);
individualList.removeAll(individualList1);
for (Employee employee : individualList) {
System.out.println(employee.getDateOfBirth());
}
}
Why removeAll is not working properly?
Applied removeAll but still getting data. Can someone please let me know why its not working?
Based on your comment: "In the employee class there is restriction. So its not allowing to modify like adding any other methods or comparator. Thats the problem. Thats why trying to find another solution for that."
If you have any unique field, maybe you can use that field to iterate over your first list to remove the required items.
import java.util.*;
public class MyClass {
public static void main(String args[]) {
List<Employee> listA = new ArrayList<>();
listA.add(new Employee("gokul", "1", "gokul#domain.com"));
listA.add(new Employee("user", "2", "user#domain.com"));
List<Employee> listB = new ArrayList<>();
listB.add(new Employee("user", "2", "user#domain.com"));
for (Employee eB : listB) {
for (Employee eA : listA) {
if (eA.id.equals(eB.id)) {
listA.remove(eA);
break;
}
}
}
for (Employee e : listA) {
System.out.println(e.name);
}
}
static class Employee {
String name;
String id;
String email;
public Employee(String name, String id, String email) {
this.name = name;
this.id = id;
this.email = email;
}
}
}
Both remove and removeAll relies on equal (and hashCode!) implementation. Most probably you didn't implement one of this methods.
Without custom implementation of equal an object equals only himself. Two variables must reference exactly the same object to be equal. Custom implementation looks like this:
class Employee {
....
public boolean equals(Object obj) {
if (obj instanceof Employee) {
Employee oo = (Employee)obj;
return Objects.equal(fullName, oo.fullName) && Objects.equal(dateOfBirth, oo.dateOfBirth)
} else {
return false;
}
}
public int hashCode() {
return Objects.hashCode(fullName, dateOfBirth);
}
}
I took fullName and dateOfBirth to ensure equality. You must manually add all the fields you think important for two objects to be equal (spoiler - it is not trivial).
If you cannot modify the object Employee there are to ways:
do it manually as described by "Gokul Nath KP"
take some other collection structure instead of ArrayList. For example TreeSet uses an optionally provided custom Comparator instance instead of equal-Method.

is there a way to manually rehash map in Java?

I have a class Employee with two attributes.
public class Employee {
private int empId;
private String empName;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return getEmpId() == employee.getEmpId() &&
Objects.equals(getEmpName(), employee.getEmpName());
//return Objects.equals(getEmpName(), employee.getEmpName());
}
#Override
public int hashCode() {
//return Objects.hash(getEmpId());
return Objects.hash(getEmpId(), getEmpName());
}
}
I'm using this class as a key in Hashmap.
Now, when I modify the original object emp in this case change name on employee object, I could not access the entry I originally saved in map. Only when I roll back the name to it's original value, I could access the object again.
This suggests me that when I change the name in Employee object, it's hash has changed and it's not stored under the correct bucket in Hashmap.
Map<Employee, String> map = new HashMap<>();;
// Set Employee with Name Shashi
Employee emp = new Employee();
emp.setEmpId(1);
emp.setEmpName("Shashi");
// Add to Map
map.put(emp, emp.getEmpName());
// Modify The Original Employee object's Name
emp.setEmpName("Shashi Bhushan");
// This object does not exist as key in map now
Assert.assertFalse(map.containsKey(emp));
// Create object with same name(used when creating)
Employee similarEmployee = new Employee();
similarEmployee.setEmpId(1);
similarEmployee.setEmpName("Shashi");
// Hashcode check will pass, equals will fail
Assert.assertFalse(map.containsKey(similarEmployee));
Assert.assertNull(map.get(similarEmployee));
// Create object with same name(modified name)
Employee anotherSimilarEmployee = new Employee();
anotherSimilarEmployee.setEmpId(1);
anotherSimilarEmployee.setEmpName("Shashi Bhushan");
// Hashcode check will fail
Assert.assertFalse(map.containsKey(anotherSimilarEmployee));
Assert.assertNull(map.get(anotherSimilarEmployee));
// Now, if I roll back the name, i could again fetch using the new created object as well.
// Since now this new object will be equivalent to the old object.
emp.setEmpName("Shashi");
Assert.assertTrue(map.containsKey(similarEmployee));
Assert.assertNotNull(map.get(similarEmployee));
One solution for the problem of being able to fetch objects in map is to make Employee class immutable.
Another theoretical solution I could think of is to rehash the map and keep the modified employee object in it's correct bucket in map but I could not see any method in hashmap that rehashes it. Please suggest if I'm thinking in the right direction or if there is any other solution for this.
P.S. all this is for the purpose of understanding hashmap, so there's no constraint on how to resolve this.
I think empId can uniquely identifies a employee.
so equals and hashCode method only need to handle empId field:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return getEmpId() == employee.getEmpId());
}
#Override
public int hashCode() {
return Objects.hash(getEmpId());
}

Overriding equals method not working on using object as key in hashmap?

I have Overridden equals method of Person class comparing the name attribute of class and if they are equal returning back true from equals method.
When i am creating the instance of Person object and using it as key in hashmap, while retrieving using a new object with same name i am not able to retrieve back the associated value from hashMap.
Below is my
import java.util.HashMap;
import java.util.Map;
public class ToStringTest {
public static void main(String[] args) {
Person person = new Person("Jack", "California");
Map<Person,String> personsMap = new HashMap<>();
personsMap.put(person,"MyCar");
Person otherPerson = new Person("Jack", "California");
System.out.println(personsMap.get(otherPerson));
}
}
class Person {
String name;
String city;
public Person(String name, String city) {
this.name = name;
this.city = city;
}
#Override
public String toString() {
return "Name : " + this.name + ", City : " + this.city;
}
#Override
public boolean equals(Object o) {
Person person = (Person) o;
if(person.name.equals(this.name)){
return true;
}
return false;
}
}
This is printing null while retrieving from using otherPerson object.
Could someone please explain this behavior.
When you add new person in map personsMap.put(person,"MyCar"); first of all if key is not null the position where the element will be put is determined. It is determined by calling hashcode method for key. There are a few steps after but it doesn't matter for this example.
Since you don't override hashcode() your person and otherPerson will have different hashcode.
The same happens when you try to get value by some key. To find position where the element is, hashcode() will be invoked. But otherPerson has different hashcode and it will lead to position where is no item (null)
equals() is used when at the same position there are many elements (in list or tree structure). Then to find the right item they will be compared by equals() method

Hashmap using which key and value to search name and phonenumber

I have requirement to put "Name" and "phonenumber" in map.
I dont understand which thing I put as key and value in hashmap.
my requirement is we can and name with phone number and search with name.
like Name:"sanjay" phoneNumber:"111";
Name:"Krish" phoneNumber:"222";
later search it by name if I search 'sanjay' it provide me sanjay's phonenumber.
and, there is more then one user with same name and one user may have more then one phonenumber.
Thanks.
If you have a Person class, make a map like: Map<Person, Collection<String>>.
Then you can find phone numbers by doing map.get(somePerson), which returns null if the person doesn't exist.
You could also consider making a PhoneNumber class, which contains the string value of a validated phone number: Map<Person, Collection<PhoneNumber>>.
Use a class wrapper:
public class Person {
private List<String> phoneNumbers;
private String fullName;
//getters, setters, constructors for field values
#Override
public boolean equals(Object o) {
if (!(o instanceof Person) {
return false;
}
Person p = (Person) o;
return this.fullName.equals(p.fullName); //and other qualifying things
}
#Override
public int hashcode() {
//account for fields that you use in #equals(Object)
}
}
Then you can index based on whatever you want:
/* Full name => People */
Map<String, List<Person>> people = new HashMap<>();
/* Number => Person */
Map<String, Person> people = new HashMap<>();
Keep in mind, if you only compare the name in equals(Object), you're back to square one. Add more things to compare to be consistent with the uniqueness.
Hash maps great power is the ability to find the values in O(1) efficiency.
For this to work, the key must be the object you search by.
For example, if you want to search by name than your key should be the name.
And since a person can have several phone numbers, the value should be a List of phone numbers.
if you want to find the person name according to the phone number you should handle this the other way around - the key would be the phone number and the value would be the person name.
Perhaps you want both...
There are good answers above, may be this will also helps
Here Student made as key by overriding hashCode() and equals() method.
public class Student {
public String studentId;
public String studentName;
public Student(String studentId, String studentName) {
this.studentId=studentId;
this.studentName =studentName;
}
#Override
public int hashCode() {
return 1234;
}
#Override
public boolean equals(Object o) {
if (o instanceof Student) {
Student student=(Student)o;
if (this.studentId.equalsIgnoreCase(student.studentId)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
Phone Number class :
public class PhoneNumber {
public String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber =phoneNumber;
}
}
Person Class :
import java.util.HashMap;
import java.util.Set;
import java.util.List;
import com.google.common.collect.Lists;
public class Person {
public static void main(String[] args) {
Student e1=new Student("e001","studentOne");
Student e2=new Student("e002","studentTwo");
PhoneNumber d1 = new PhoneNumber("9999999998");
PhoneNumber d2 = new PhoneNumber("9999999999");
List listOfPhoneNumbersOfStudentOne = Lists.newArrayList(d1,d2);
PhoneNumber d3 = new PhoneNumber("9999999997");
PhoneNumber d4 = new PhoneNumber("9999999996");
List listOfPhoneNumbersOfStudentTwo = Lists.newArrayList(d3,d4);
/* Here Student made as key by overriding hashCode() and equals() method.*/
HashMap<Student, List<PhoneNumber>> map=new HashMap<Student, List<PhoneNumber>>();
map.put(e1, listOfPhoneNumbersOfStudentOne);
map.put(e2, listOfPhoneNumbersOfStudentTwo);
Set<Student> key=map.keySet();
for (Student student : key) {
System.out.println(student.studentId+" "+student.studentName +" ");
}
}
}
public class Assignment4 {
HashMap map = new HashMap<>();
public void addContact(String name, Integer number) {
map.put(name, number);
}
public void getphoneNumber(String name) {
if (map.containsKey(name)) {
Integer a = map.get(name);
System.out.println("Contact of " +name+" is " + a);
}
}
public static void main(String[] args) {
Assignment4 a4 = new Assignment4();
a4.addContact("vishal", 10345);
a4.addContact("sachin", 30456);
a4.addContact("sai", 30458);
Scanner s=new Scanner(System.in);
System.out.println("Enter name to get contact details");
a4.getphoneNumber(s.next());
s.close();
}
}

Categories