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());
}
Related
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.
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
I have a custom class as Student which has two different equals methods (equals, equals1) and have two collections of Student object as list1 and list2. When I do use list1.retainAll(list2), it needs to be used equals1 mechanism instead of eqauls method.
Is this possible or Do we have any API to mention the equals method name when we do have multiple equals mechanism in our customer class.
Example:
class Student {
public String name;
public Student(String name) {
this.name = name;
}
public boolean equals(Object obj) {
return super.equals(obj);
}
public boolean equals1(Object obj) {
if(obj == null) return false;
if(!(obj instanceof Student)) return false;
Student student = (Student) obj;
if(this.name.equals(student.name)) {
return true;
} else {
return false;
}
}
}
public class Demo {
public static void main(String[] args) {
List<Student> list1 = new ArrayList<Student>();
list1.add(new Student("AAA"));
list1.add(new Student("BCD"));
List<Student> list2 = new ArrayList<Student>();
list2.add(new Student("AAA"));
list2.add(new Student("CDE"));
// Intersection of list1 and list2
list1.retainAll(list2);
for (Student student : list1) {
System.out.println(student.name);
}
}
}
The expected result is [AAA] in case if equals1 method is used but in this case, the default equals method is getting executed so that the result is empty.
How do we use custom equals method for collection algorithm.
equals() is special
remember that the equals() is special because the whole collections API relies on it.
Furthermore collections API (at least anything with "Hash" in its name like HashSet or HashTable) relies on the relationship between equals() and hashcode(): The deal is when ever equals() returns true both objects return the same value from hashcode(). On top of that the value retuned by hashcode() must not change during an objects lifetime.
Your implementation has the potential to break this rules forcing the collections API zu fail because your equals1() method uses the mutable field name.
You can create equals wrapper that wrap you entity and your equals
its will be something like :
public class EqulasWrapper<T extends CustomHashCode> {
T entity;
BiFunction<T, T, Boolean> customEquals;
public EqulasWrapper(T entityClass, BiFunction<T, T, Boolean> func){
this.entity = entityClass;
this.customEquals = func;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqulasWrapper<T> that = (EqulasWrapper<T>) o;
return this.customEquals.apply(this.entity, that.entity);
}
#Override
public int hashCode() {
return entity.getCustomHashCode();
}
public static class Person implements CustomHashCode {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
#Override
public int getCustomHashCode() {
return age;
}
}
public T getEntity() {
return entity;
}
public BiFunction<T, T, Boolean> getCustomEquals() {
return customEquals;
}
public static void main(String[] args) {
Person person = new Person(10, "Dan");
BiFunction<Person, Person, Boolean> myCustomEqualsFunc =
(p1, p2) -> p1.age == p2.age;
EqulasWrapper<Person> personEqulasWrapper = new EqulasWrapper<>(person, myCustomEqualsFunc);
Set<EqulasWrapper> mySet = new HashSet<>();
mySet.add(personEqulasWrapper);
}
The CustomHashCode interface look like:
public interface CustomHashCode {
int getCustomHashCode();
}
short explanation
your set will use your custom Hash code that your entity implement and the custom equals your give him.
so you can create different collection with type EqulasWrapper class
and design your hash code and your equals function has you wish, the collection instead of using original hash code and equals will use with your wrapper
Hope I helped a bit
if you have any questions I would love to hear.
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.
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();
}
}