Reputation: 2758
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?
Upvotes: 2
Views: 566
Reputation: 723
Here are my notes from reading Effective Java 2nd Edition:
Equals must adhere to general contract:
non-null x
: x.equals(x) == true
non-null x,y
: x.equals(y) <==> y.equals(x)
non-null x,y,z
: x.equals(y) and y.equals(z) ==> x.equals(z) == true
x.equals(y) == true
, then for all invocations must return true if no change in x
and y
x
: x.equals(null) == false
High Quality equals method:
x == x
)null
)Final caveats:
Hashcode direct quote from Effective Java 2nd Edition
For each significant field f
in your object (each field taken into account by the
equals method, that is), do the following:
(f ? 1 : 0)
.byte, char, short, or int, compute (int) f.
long, compute (int) (f ^ (f >>> 32)).
float, compute Float.floatToIntBits(f).
double, compute Double.doubleToLongBits(f)
, and
then hash the resulting long
.equals
, recursively
invoke hashCode
on the field. If a more complex comparison is
required, compute a “canonical representation” for this field and
invoke hashCode on the canonical representation. If the value of the
field is null
, return 0
(or some other constant, but 0 is traditional).result = 31 * result + c;
Return result.
so following these rules:
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Employee)) {
return false;
}
Employee other = (Employee) obj;
return super.equals(other) &&
Double.compare(this.salary, other.salary) == 0 &&
this.hireDay.equals(other.hireDay);
}
In your case though it seems like id
should already uniquely identify any person, so you should just use that for comparison in equals and not override it in any subclass.
Upvotes: 1
Reputation: 136
If you want to implement equals and hashcode method use eclipse just right click in file go to source and select generate equals() & hashcode() with the fields that you need , just like below :
Upvotes: 1
Reputation: 3765
Is it possible for two people to ever have the same id
? It shouldn't. So that logic extends to the Employee
class, which means implementing equals
and hashCode
in the Person
class is enough.
At this point, since you're only dealing with an int
, you can use Integer.hashCode(id)
for the hashCode
and just compare the values for the equals
.
Upvotes: 1