I've a class called Skill
as:
public class Skill {
private final int type;
private final int level;
public Skill(int type, int level) {
this.type = type;
this.level = level;
}
// Getters
@Override
public int hashCode() {
int h = 17;
h = 31 * h + type;
h = 31 * h + level;
return h;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof Skill) {
Skill that = (Skill) obj;
return this.type == that.type && this.level == that.level;
}
return false;
}
}
Another called PSkill
as:
public class PSkill extends Skill {
private final int preferenceLevel;
private final boolean mandatory;
public PSkill(int type, int level, int preferenceLevel, boolean mandatory) {
super(type, level);
this.preferenceLevel = preferenceLevel;
this.mandatory = mandatory;
}
// Getters
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof PSkill) {
PSkill that = (PSkill) obj;
return super.equals(obj)
&& this.preferenceLevel == that.preferenceLevel
&& this.mandatory == that.mandatory;
}
return false;
}
}
My requirement:
Search a set of PSkill
objects to find a match for Skill
object.
For e.g.: Skill - type:1, level:2
is a match for PSkill - type:1, level:2, preferenceLevel: any, mandatory: any
When I run the below, it works.
public class Invoker {
public static void main(String[] args) {
Set<PSkill> skills = new HashSet<>(Arrays.asList(new PSkill(1, 1, 1, true), new PSkill(1, 2, 1, true)));
System.out.println(skills.contains(new Skill(1, 1))); // prints true
System.out.println(skills.contains(new Skill(1, 3))); // prints false
}
}
And I know why because the hashCode()
implementation for both types is same and in HashSet
implementation, key.equals(k)
is used and key in my case is Skill
object and hence, the equality works.
From HashSet implementation
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) // here
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) // and here
return e;
} while ((e = e.next) != null);
}
}
return null;
}
I know I broke the hashCode()
and equals()
contract. But the code is working as it should, i.e. check if a Skill
matches any of the PSkill
s in the set.
My question is:
Is the equality check key.equals(k)
in HashSet
implementation dependent and could be reversed in a future release i.e. k.equals(key)
and the code will stop working?
Also, any better ways to do it which could make it less fragile without simply looping over the collection? Thanks
This behavior is certainly not guaranteed. The contract is that if you provide correct implementations of hashCode and equals, you get a correct implementation of a hashset. If you don't follow your end of the contract, you don't have any guarantees on how well a set will work.