I have the following abstract Person class:
import java.util.Objects;
public abstract class Person {
protected String name;
protected int id;
public Person(String name, int id) {
this.name = name;
this.id = id;
}
public abstract String description();
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(!(obj instanceof Person)) return false;
return Objects.equals(this.name, ((Person) obj).name) &&
this.id == ((Person) obj).id;
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.id);
}
}
And Now I have a subclass of Person called Employee:
import java.time.LocalDate;
import java.util.Objects;
public class Employee extends Person {
private double salary;
private LocalDate hireDay;
public Employee(String name, int id, double salary, int year, int month, int day) {
super(name, id);
this.salary = salary;
this.hireDay = LocalDate.of(year, month, day);
}
@Override
public String description() {
return "Employee with a salary of " + this.salary;
}
@Override
public int hashCode() {
return super.hashCode() + Objects.hash(this.salary,this.hireDay);
}
@Override
public boolean equals(Object obj) {
return super.equals(obj) &&
Double.compare(this.salary, ((Employee) obj).salary) == 0
&& Objects.equals(this.hireDay,((Employee)obj).hireDay);
}
To implement an equals method properly, it must conform to the following contract.
Reflextive: x.equals(x) is always True
Symmetric: x.equals(y) is equivalent to y.equals(x)
Transitive: x.equals(y) and y.equals(z) implies x.equals(z) is true
When I call the equals() method of the superclass inside the subclass, I first ensure that all objects being compared are subclasses of the superclass. This issue resolves the problem of comparing mixed-types and takes care of the contract mentioned above. I no longer have to use the following implementation of equals:
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
else if(obj == null || this.getClass() != obj.getClass()) return false;
Employee other = (Employee) obj;
return Objects.equals(this.name, other.name) &&
Double.compare(this.salary, other.salary) == 0 &&
Objects.equals(this.hireDay, other.hireDay);
}
Namely, I no longer have to check explicitly whether the current object (this) is of the same class as obj because of the method in the superclass that uses the instance of operator.
Is it more robust to put that implementation in the equals operator of the superclass or is it better to use the more explicit test in the subclass using the getClass() method in order to conform to the contract?
In terms of the hashCode() method, I hash the private instance fields specific to the subclass and simply add that to the result of the hash method in the superclass. I couldn't find any documentation that shows whether or not this is the proper way to implement the hashCode() function in general or in an inheritance hierarchy. I have seen code where people have explicitly specified their own hash functions.
I apologize if my questions are too general but I tried my best to ask them without being too ambiguous.
EDIT:
I asked Intellij to implement an equals and hashcode method and it decided to go with the last implementation that I posted above. Then, under what circumstances would I use instance of in the superclass? Would it be when I'm implementing a final equals method in the superclass such as only comparing Person objects based on user id?

