Maintaining the hashCode contract for a specific condition is () depending on two integers
I have a base class with a structure:
class Employee {
int eId;
String eName;
Employee(int id, String name) {
this.eId= id;
this.eName= name;
}
The equality conditions are such that equals()
must return true if one of the following conditions is true:
-
eId
- the same. -
eName
- the same. - The lengths are the
eName
same.
I had no problem with overriding equals()
, however, in order to preserve the contract of the hashcode, I have to override hashCode()
. So hashCode must depend on eId
and eName.length()
(if eName
equal, their lengths will also be equal). Thus, there are four cases:
Employee e1 = new Employee(4, "John");
Employee e2 = new Employee(3, "Jane");
Employee e3 = new Employee(4, "Jim");
Employee e4 = new Employee(7, "Random");
hashCode()
must return the same value e1
, e2
and e3
another meaning for e4
. I cannot think of any logic to satisfy this requirement.
Here's the exact problem:
Create a class (with parameter name, id, etc.). Show that if 2 objects of this class are to be compared, they must return true in any of the following cases:
and. The identifier for both values is the same.
Q. The name of both is the same.
C. The length of the names is the same.
Make sure the HashCode contract must not be violated.
source to share
You are in trouble because your concept of equality is incompatible. In particular, it is not transitive, which requires a contract .equals()
.
It is transitive: for any non-null reference values
x
,y
andz
if itx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, then itx.equals(z)
must returntrue
.
In your definition, e1
equal e2
and e3
but e2
not equal e3
. This is incompatible with Java's notion of equality. This is why you have found it difficult to determine a reasonable implementation .hashCode()
.
However, what you can do is define a custom Comparator
(or Ordering
if you are using Guava). In most use cases (like sorting, searching, or filtering) you should use a separate instance Comparator
, just like the method .equals()
. You are effectively trying to define equivalent objects, not equal objects.
If you can't use a separate one Comparator
for whatever reason, your object Employee
will be fundamentally inconsistent and problematic even if you have to implement "workable" .hashCode()
.
source to share
I think you cannot write sequential hashCode
in your case, because yours equals
violates the contract of the Object.equals
method, namely transitivity:
It is transitive: for any non-null reference values
x
,y
andz
if itx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, then itx.equals(z)
should returntrue
.
Let's assume you have this code:
Employee a = new Employee(1, "John");
Employee b = new Employee(1, "James");
Employee c = new Employee(2, "James");
In this case, with your operation equals a.equals(b)
and b.equals(c)
but not a.equals(c)
.
I would suggest revisiting the implementation equals
. Yours probably Employee
has either eId
or eName
, so it would be better to add a field boolean
that says whether to use eId
or eName
. So you can easily implement equals
and hashCode
. Thus, the implementation can be like this (if for simplicity eName
it cannot be null
):
class Employee {
boolean useName;
int eId = 0;
String eName;
Employee(int id) {
this.eId = id;
this.useName = false;
}
Employee(String name) {
this.eName = name;
this.useName = true;
}
@Override
public int hashCode() {
return useName ? eName.length() * 1337 : eId * 7331;
}
@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 (useName != other.useName)
return false;
if (useName) {
if (eName.length() != other.eName.length())
return false;
} else {
if (eId != other.eId)
return false;
}
return true;
}
}
source to share