Search a base class's object in a Set of Derived class

134 Views Asked by At

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 PSkills 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

1

There are 1 best solutions below

0
On BEST ANSWER

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.