Chris311
Chris311

Reputation: 3992

Compare classes extending the same superclass

Consider we got a class MyEntity which has some field with getters and setters. Furthermore the classes EntityA and EntityB extend MyEntity. There are some fields in MyEntityA that are not in MyEntityB and vice versa. As we are talking about entities, EntityA and EntityB do have their own equals-methods looking like

@Override
public boolean equals(Object other) {
    if ((this == other))
        return true;
    if ((other == null))
        return false;
    if (!(other instanceof EntityA))
        return false;
    EntityA castOther = (EntityA) other;

    return ((this.getId() == castOther.getId()) || (this.getId() != null && castOther.getId() != null && this.getId().equals(castOther.getId())));
}

This equals-method is neded for hibernate to identify the unique entity.

Now I want to compare an instance of EntityA with an instance of EntityB. I define them to be identical, if all the fields from MyEntity match.

To check this I let eclipse generate the equals method for me and copy it to a method like isEqualWithRegardsToContent(MyEntity other).

I see one big problem with this approach: If someone ever adds a new column to either one of the entities and doesn't update the isEqualWithRegardsToContent(MyEntity other)-method, it get's buggy: The entities might be considered as equal with regards to content, although they aren't.

I don't see that a unit-test would help here.

Do you have any best practices?

Upvotes: 2

Views: 4548

Answers (4)

Alex Salauyou
Alex Salauyou

Reputation: 14348

Say, you have equals() method in superclass that compares common properties.

In subclass equals() you can first call super.equals(), then, if compared object is also of this class, compare only specific properties. So in EntityA you write:

@Override
public boolean equals(Object o) {
    boolean eq = super.equals(o);
    if (eq && o instanceof EntityA) {
        EntityA e = (EntityA) o;
        return Objects.equals(this.propOne, e.propOne) 
            && Objects.equals(this.propTwo, e.propTwo)
            && // compare other properties
    } else 
       return eq;
}

Such, objects of the same concrete class will be compared by full set of properties, including common and specific properties, and instances of different classes will be compared only by common properties. Though this is non-standard way which violates transitive property of the contract, it may solve your particular problem.

Upvotes: 2

Serge Ballesta
Serge Ballesta

Reputation: 149025

If I correctly understand your problem:

  • you have a class named MyEntity lacking a getId field
  • you have 2 subclasses EntityA and EntityB both defining a id field but form different generators
  • when comparing 2 objects of class EntityA (resp. EntityB) they compare equal if and only if their id fields have same value - I assume that in that case all fields inherited from MyEntity have same value
  • when comparing a EntityA object with a EntityB one, they should compare equal if and only if all the fields inherited from MyEntity are equal.

Here is what you could do:

  • declare an equals method in MyEntity that returns true if and only if all fields compare equal - if you cannot use the equals method for that, name the method ABequals (for example)
  • declare the equal method in subclass EntityA (resp. EntityB)

    @Override
    public boolean equals(Object other) {
        if ((this == other))
            return true;
        if ((other == null))
            return false;
        if (!(other instanceof EntityA))
            return MyEntity.equals(other);  // or return ABequals(other);
        EntityA castOther = (EntityA) other;
    
        return ((this.getId() == castOther.getId()) || (this.getId() != null && castOther.getId() != null && this.getId().equals(castOther.getId())));
    }
    

In that case you must implement a hash method in the MyEntity class that uses all the fields and you should not overide it in subclasses. That way you ensure that two objects that compare equal will have same hash value - the above definition already guaranties that the equals function is an equivalence relation.

Upvotes: 0

Darth Android
Darth Android

Reputation: 3502

A class' equals() method should only work for other instances of that class. If you want to compare EntityA and EntityB with equals(), then your equals() method should be defined on MyEntity (along with hashCode()) and both EntityA and EntityB should not have equals() methods themselves. Otherwise, you are very likely to run afoul of the contract for equals(). Libraries such as Hibernate depend upon equals() properly adhering to the contract.

Upvotes: 0

radai
radai

Reputation: 24192

generally speaking its impossible to have a fully functioning equals when dealing with inheritance trees. superclass.equals(subclass) will not return the same result as subclass.equals(superclass), breaking symmetry (which is the basic contract for equal - if a.eq(b) then also b.eq(a)).

you could implement equals() only at the top level, thereby comparing the entire hierarchy from the point of view of the superclass. this will work, but will not compare by "all fields" (only those in the superclass). commonly entities may be compared by just their primary keys

personally i dont usually mix the 2 - equals() and hashcode() i reserve for simple data-storage classes (or keys used to lookup in maps) that are not polymorphic.

see a details coverage here - http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html

Upvotes: 2

Related Questions