Overriding equals/hashCode on cross referencing classes in Java causes StackOverflowError - java

I have two classes that represent two different database entities. Their relationship is 1:m in db and it is represented in class structures something like this:
public class Company {
private List<Employee> employees;
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
public class Employee {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Now I want to override equals/hashCode on these classes. Eclipse generates the following code for me:
public class Company {
private List<Employee> employees;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((employees == null) ? 0 : employees.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;
Company other = (Company) obj;
if (employees == null) {
if (other.employees != null)
return false;
} else if (!employees.equals(other.employees))
return false;
return true;
}
}
public class Employee {
private Company company;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((company == null) ? 0 : company.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;
Employee other = (Employee) obj;
if (company == null) {
if (other.company != null)
return false;
} else if (!company.equals(other.company))
return false;
return true;
}
}
If I run the following test:
public class EqualsTest {
#Test
public void testEquals() {
Company company1 = new Company();
Employee employee1 = new Employee();
employee1.setCompany(company1);
company1.setEmployees(Arrays.asList(employee1));
Company company2 = new Company();
Employee employee2 = new Employee();
employee2.setCompany(company2);
company2.setEmployees(Arrays.asList(employee2));
assertThat(company1, is(company2));
}
}
I expect it to pass because both company1 and company2 have equal lists of employees, but it fails with StackOverflowError:
java.lang.StackOverflowError
at java.util.AbstractList$Itr.<init>(AbstractList.java:318)
at java.util.AbstractList$Itr.<init>(AbstractList.java:318)
at java.util.AbstractList$ListItr.<init>(AbstractList.java:377)
at java.util.AbstractList.listIterator(AbstractList.java:315)
at java.util.AbstractList.listIterator(AbstractList.java:284)
at java.util.AbstractList.equals(AbstractList.java:502)
at com.test.Company.equals(Company.java:37)
at com.test.Employee.equals(Employee.java:35)
at java.util.AbstractList.equals(AbstractList.java:507)
at com.test.Company.equals(Company.java:37)
at com.test.Employee.equals(Employee.java:35)
at java.util.AbstractList.equals(AbstractList.java:507)
at com.test.Company.equals(Company.java:37)
at com.test.Employee.equals(Employee.java:35)
...
I understand that the reason for this failure is cross reference in classes and thus equals/hashCode methods. But how should I implement equals/hashCode to avoid infinitive recursion?

As it is now, the identity of a company is defined solely by its employees. Likewise, the identity of an employee is defined solely by its company. Do you see how that leads to a mutual logical dependency?
You need to break that logical dependency in your code. How would you logically uniquely identify a company and an employee? Typically you'd do this with some sort of meaningful unique identifier: a name (string), a number (int/long), or some similar combination of primitive fields.

Imho there are 2 versions available. I assume company should be the "leading" class, storing the employees.
version: In the employee equals, use the "==" to check for object equality on company (not very nice)
version: assign your company a unique ID and compare that only that company ID in employee equals
hth

Do not compare the list of employees within the Company.equals method. Are there other attributes of Company that are meaningful and could be used to perform the comparison within equals, like a name? Or Stock Symbol?

You have inadvertently set up a recursive dependency between Company and Employee. The Company#hashCode() method needs to compute the individual hashcodes of every Employee, and the Employee#hashCode() method depends on the Company's hashcode, leading to an infinite recursion.
A company object's hashcode should not depend on the employees in it. The hash code is in some sense the "identity" of the object, which shouldn't change when a new employee is added to it. Same for Employee. The Employee's identity shouldn't change just because he/she moves to a different company.
You'll have to redefine those methods in terms of some meaningful identity attribute. Your code doesn't show it, but both Company and Employee must have some other member variables, such as a name. Base the hashCode and equals implementations on that attribute.

Related

Inserting distinct values from XML into database table

I have XML as follows:
<employee>
<code>13</code>
<label>Admin</label>
</employee>
<employee>
<code>13</code>
<label>Admin</label>
</employee>
<employee>
<code>09</code>
<label>Logistics</label>
</employee>
In my Oracle database, I have 2 columns, namely CODE1, CODE2.
The data should be inserted like CODE1= 13 and CODE2= 09.
But, currently what is happening is that CODE1= 13 and CODE2= 13. And 09 is not been inserted in database.
It just stores the first 2 values ignoring the rest.
My requirement is that, duplicate values must be inserted only once in DB.
Expected result:
CODE1= 13, CODE2= 09
Following is my java code:
for (int i = 0; i < 2; i++) {
final int count = i + 1;
String code = null;
final Emploi[] employee = tabLieuTrav.getEmployee();
code = employee[i].getCode();
if (code != null) {
mapParam.addParamValue(CODE + count,
code);
} else {
mapParam.addParamValue(CODE + count, null,
Types.VARCHAR);
}
getCode() returns the value (e.g. 13) from tag .
Thanks in advance.
try with following solutions,
firstly you should create a Employee class including with hasCode() and equals() methods as follows,
public class Employee {
private int code;
private String lable;
public Employee(int code, String lable) {
super();
this.code = code;
this.lable = lable;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + code;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (code != other.code)
return false;
return true;
}
}
above hasCode() and equals() methods are generated by eclipse ide. you can creates these methods manually like this,
#Override
public boolean equals(Object obj) {
if (obj instanceof Employee) {
return Objects.equals(code, ((Employee) obj).code);
}
return false;
}
#Override
public int hashCode() {
return this.code;
}
equals Method : Indicates whether some other object is "equal to" this one. for more info
hashCode Method : Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap. for more info
then, add employee array to ArrayList. because below mentioned methods describe how to get distinct values from ArrayList.
Emploi[] employee = tabLieuTrav.getEmployee();
List<Employee> empList = new ArrayList(Arrays.asList(employee));
then, you can use one of the following methods for remove duplicate values from ArrayList (empList)
method one, remove duplicates from ArrayList using Set (A collection that contains no duplicate elements) for more info
HashSet<Employee> uniqueEmployee = new HashSet(empList);
method two, remove duplicates from ArrayList using java 8 stream distinct method (return distinct element from collection) for more info
List<Employee> uniqueEmployee = empList..stream().distinct().collect(Collectors.toList();
finally, you can use uniqueEmployee collection as follows,
for (Employee employee : uniqueEmployee) {
code = employee.getCode();
if (code != null) {
mapParam.addParamValue(CODE + count, code);
} else {
mapParam.addParamValue(CODE + count, null, Types.VARCHAR);
}
}

How to test the child class for equality in Java?

I am working on a project, but I got stuck in some issue. I need to test equality of child class (DiscountProduct).
Relation between classes can be understood as: Order class has-a Product and Product is parent of DiscountProduct. I want to test DiscountProduct for equality. Any help is much appreciated. Thank you!
Below is the code:
Order Class:
public class Order implements Comparable<Order>{
private int quantity;
//composition has-a relationship
private Product product;
public Order(int quantity, Product product) {
this.quantity = quantity;
this.product = product;
}
public boolean equals(Object obj){
if (obj instanceof Order) {
return product.equals(obj) && quantity == ((Order) obj).quantity;
}
return false;
}
public Product getProduct() {
return product;
}
}
Product class
public class Product implements Comparable<Product> {
private String productCode;
private String description;
private int unitPrice; //pence
public Product(String productCode, String description, int unitPrice) {
this.productCode = productCode;
this.description = description;
this.unitPrice = unitPrice;
}
//equals method to test product for equality.
public boolean equals(Object obj){
if (this == obj){
return true;
}
if (obj == null || !(obj instanceof Product)){
System.out.println("-");
return false;
}
Order other = (Order) obj;
if (!productCode.equals(other.getProduct().getProductCode()))
return false;
if (!description.equals(other.getProduct().getDescription()))
return false;
if (unitPrice != (other.getProduct().getUnitPrice()))
return false;
return true;
}
}
DiscountProduct class
public class DiscountProduct extends Product {
private double discountRate;
public DiscountProduct(String productCode, String description, int unitPrice, double discountRate) {
super(productCode, description, unitPrice);
this.discountRate = discountRate;
}
//equals method to test discount product for equality.
public boolean equals(Object obj){
Order other = (Order) obj;
//how to test for the equality for the discountProduct's field discountRate?
//does I need to add some method in Order class to get the discountRate of object of Order class,
//because relation is 'Order has Product' and then there is a parent-child relation
//between product and DiscountProduct class.
}
}
DiscountProduct could do something like:
#Override // always always always use that annotation when overriding!
public boolean equals(Object obj){
.. this == obj test
if (obj == null || !(obj instanceof DiscountProduct)){
return false;
}
if (super.equals(obj)) {
cast to DiscountProduct and check discountRate
The point here is:
you really only want objects to be equal that are of the same class. Otherwise there is a high chance that you get the necessary if a.equals(b) then b.equals(a) rule wrong
then you want to re-use the existing implementation of the parent class
to then finally, compare the child class field(s)
It might also make sense to declare that "last" version of equals() to be final, but that really depends on context.
In this part of the code:
public class Order implements Comparable<Order>{
...
public boolean equals(Object obj){
if (obj instanceof Order) {
return product.equals(obj) && ...;
}
...
}
}
A Product is comared with an Order. This comparison should obviously return false since a Product is no Order. What you probably meant to write was:
return product.equals(obj.product) && ...;
A remark on your code: There is a contract between Object::equals and Object::hashCode:
The general contract of hashCode is:
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.
To conform with this contract, one should always override hashCode() when one overridest equals(...).
I would also encourage you to set methods equals(...) and hashCode() final. Otherwise, the contract of equals(...) could be violated.

Verify there is a combination of unique string

class Details{
String name;
String age;
String email;
String location;
}
1) If there is List of Details as in List<Details> how to verify for a combination of name and email collectively unique. (i.e) For a single email address there cant be two name entry.
2) How to verify the combination of all fields in the class file is unique.
what would be a perfect data structure to address this ?.
You can hash values by a separator like #, and then find that all uniques or not. Hash value for a Details is name + "#" + "email in the first case, and is name + "#" + age + "#" + email + "#" + location in the second case.
You can using Hashmap to find duplicates if there is any with the specified key (or hash) for each instance of Details.
If you need to achieve unique values-only, you should use Set. You have to use your own equals & hashCode implementation, for example, for case 1):
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Details details = (Details) o;
return Objects.equals(name, details.name) &&
Objects.equals(email, details.email);
}
#Override
public int hashCode() {
return Objects.hash(name, email);
}
If you need all Details members to be unique just update hash & equals implementation with needed properties.
When you overrides an equals method within any object, you can perfectly check the equality of that Object with another one even though they are residing somewhere different within the memory.
So the below code
myList.contains(myObject);
will respond if there is an object within myList that the equals method return true with myObject.
In all major IDEs (like intelliJ, netbeans, eclipse, etc) the IDE will help you to override the equals method accurately. here is the auto-generated code for overriding equals method using intelliJ IDE
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Details details = (Details) o;
return Objects.equals(name, details.name) &&
Objects.equals(email, details.email);
}
Now if you want to avoid duplicate to be added inside your List, you should use Set instead of a List. A Set will add a new element if the combination of hashCode and equals method are different (comparing the existing object and newly intended to add object)
So we have to override hashCode method (generated by intelliJ IDE) if we want to use a flavor of Set class:
#Override
public int hashCode() {
return Objects.hash(name, email);
}
now if you create a Set and try to add two objects with similar name and email inside that Set, the set will only add the first unique name and email address, even thou the other fields of the second object have different values
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
class Details {
String name;
String age;
String email;
String location;
public Details(String name, String age, String email, String location) {
this.name = name;
this.age = age;
this.email = email;
this.location = location;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Details details = (Details) o;
return Objects.equals(name, details.name) &&
Objects.equals(email, details.email);
}
#Override
public int hashCode() {
return Objects.hash(name, email);
}
#Override
public String toString() {
return String.format("(name: %s, age: %s, email: %s, location: %s)", name, age, email, location);
}
public static void main(String[] args) {
Set<Details> mySet = new HashSet<>();
mySet.add(new Details("Mehdi", "12", "123#xyz.com", "Los Angeles"));
mySet.add(new Details("Mehdi", "34", "123#xyz.com", "Las Vegas"));
System.out.println(mySet);
}
}
This is the whole test app. There is something else that worth mentioning. if in any case you have saved your data inside a list and you want to remove the duplicate based on the rules you have (ex name, email) You can try this approach:
Set<Details> mySet = new HashSet<>(myList);
Which myList is your list. so in your app that will be like this:
public static void main(String[] args) {
List<Details> myList = new ArrayList<>();
myList.add(new Details("Mehdi", "12", "123#xyz.com", "Los Angeles"));
myList.add(new Details("Mehdi", "34", "123#xyz.com", "Las Vegas"));
Set<Details> mySet = new HashSet<>(myList);
System.out.println(mySet);
}
and here is the result without any duplicate:
[(name: Mehdi, age: 12, email: 123#xyz.com, location: Los Angeles)]
Besides of using a hash as propposed by #OmG, you could also use a TreeSet with the key being a concatenation of the unique fields, also using a separator between them.
A Set only admits unique keys.

Java POJO collection attributes

I have a POJO something like the one mentioned below. Here I'm referring Set collection attribute in POJO1. I understand that set does not contain duplicate. Do I need to override equals() and hashCode() methods in POJO2? Using a Set here is not really going to helpful unless we override equals and hashCode methods? Please help me to understand little bit more on this context!
public class POJO1 {
private String name;
private Set<POJO2> pj2;
public Company(){
pj2 = new HashSet<>();
}
//setter and getter methods
}
Yes the only way for Java to understand which objects are duplicates is to call equals() method. Default implementation of equals() checks that references of two objects point to the same location in memory.
But depending on exact implementation of your Set you might need to override hashCode/equals or implement Comparable interface.
Since you put objects of POJO2 into HashSet you need to verride hashCodeequalsmethods inPOJO2` class.
You do like this
import java.util.Set;
public class POJO1 {
private String name;
private Set<POJO2> pojo2;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<POJO2> getPojo2() {
return pojo2;
}
public void setPojo2(Set<POJO2> pojo2) {
this.pojo2 = pojo2;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
POJO1 pojo1 = (POJO1) o;
if (name != null ? !name.equals(pojo1.name) : pojo1.name != null) return false;
return pojo2 != null ? pojo2.equals(pojo1.pojo2) : pojo1.pojo2 == null;
}
#Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (pojo2 != null ? pojo2.hashCode() : 0);
return result;
}
}
Learn more at https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#equals(java.lang.Object)

How to check if a string exists in a object which is stored in a hashmap?

I have created a Employee class with 3 parameters.
Id
Name
Age
Requirement: Search based on Name. This is a case where all employees have a unique name. Its mandatory to add the objects with key as id. There are rare cases where it is required to search based on name.
What i have done :
Within the class I am overriding hashCode and Equals method.
I am adding a list of these objects into the hashmap with id as key and value as Employee object
But while adding or searching from a hashmap both the methods do not get called
So what is the use of these methods in terms on hasmap?
Employee Class
public class Employee {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int hashCode() {
return name.hashCode();
}
public boolean equals(Employee emp) {
if (emp == null)
return false;
else if (emp.name.equalsIgnoreCase(this.name))
return true;
else
return false;
}
}
Main Method:
public class HashMapTest {
public static void main(String[] args) {
Employee emp1=new Employee();
emp1.setId(1);
emp1.setName("Maclean");
emp1.setAge(24);
Employee emp2=new Employee();
emp2.setId(2);
emp2.setName("Sampath");
emp2.setAge(25);
Employee emp3=new Employee();
emp3.setId(3);
emp3.setName("Achar");
emp3.setAge(27);
Employee emp4=new Employee();
emp4.setId(4);
emp4.setName("Sudheer");
emp4.setAge(25);
Employee emp5=new Employee();
emp5.setId(5);
emp5.setName("Kunder");
emp5.setAge(25);
HashMap<Integer, Employee> empmap=new HashMap();
empmap.put(emp1.getId(), emp1);
empmap.put(emp2.getId(), emp2);
empmap.put(emp3.getId(), emp3);
empmap.put(emp4.getId(), emp4);
empmap.put(emp5.getId(), emp5);
Employee emp=new Employee();
emp.setName("Maclean");
System.out.println(empmap.containsValue(emp));
System.exit(1);
}
}
Update Solution:
Thanks for all the answers.
1. hashCode method gets called only if the Key is a object and the method exists within the Key Class
2. Equals(Employee emp) is causing function overloading instead of overriding. I should have used equals(Object o)
Changes in the code to resolve the issue
#Override
public boolean equals(Object o) {
if (o == null)
return false;
if (!(o instanceof Employee))
return false;
Employee emp = (Employee) o;
if (emp.name.equalsIgnoreCase(this.name))
return true;
else
return false;
}
You are not overriding Object.equals(Object o), which you need to do. You are overloading it. That's why it's not being called.
Try this equals() instead:
public boolean equals(Object o) {
if (o == null)
return false;
if (!(o instanceof Employee))
return false;
Employee emp = (Employee) o;
if (emp.name.equalsIgnoreCase(this.name))
return true;
else
return false;
}
Within the class I am overriding hashCode and Equals method.
[...]
But while adding or searching from a hashmap both the methods do not get called
So what is the use of these methods in terms on hasmap?
If you have a Map<Key, Value>, and you call put or get on that map, then hashCode and equals are called on the Key class, not on the Value class.
In your case, that means that if you do empmap.put(emp1.getId(), emp1); then it checks the hash of emp1.getId() and whether that's already in the map. So it's normal that those methods are not called on your Employee class.
Also, if id is the "unique" attribute, then Employee.hashCode should probably be based on that (and equals, too, to be consistent with hashCode), and as noted in another answer, Employee.equals should accept any Object as parameter.
I did not test, but try it with
HashMap<Integer, Employee> empmap=new HashMap<>();
or even
HashMap<Integer, Employee> empmap=new HashMap<Integer, Employee>();
This can be done very nicely in Java 8 using streams.
empmap.values().stream().anyMatch(emp.getName().equals(searchedName));
This takes the set of all entries in the map and see if the stream matches any entry that has a name equal to your searchedName.
In a comparable you can also fetch all matching names by using Stream.filter()
Implementing a different version of equals/hashcode is tricky because it changes the behavior of the class in many ways.
The easiest solution for your problem is to add this method to the Employee class.
Silently, HashMap uses the equals(Object o) of a value object (in this case Employee) for checking the existence of that object.
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (name != null ? !name.equals(employee.name) : employee.name != null) return false;
return true;
}
Be careful, This implementation of equals(Object o) just works on the name and it does not check other fields.
Employee emp=new Employee();
emp.setId(10);
emp.setName("Maclean");
emp.setAge(240);
System.out.println(empmap.containsValue(emp));
System.out.println(emp1.equals(emp));
is
true
true

Categories

Resources