Adrian Shum
Adrian Shum

Reputation: 40056

Java Type Check in equals(): instanceof vs getClass()

First, I am not asking for the difference between them.

I have an idea in mind and I would like more brain to verify, which seems able to take (most of) the pros of using both approach.

In brief, equals() in Java usually looks like this.

public boolean equals(Object other) {
    if (other == null) return false;
    if (this == other) return true;
    if (this and other is not of the same type) return false
    // then actual equals checking
}

The problem lies in the "this and other is not of the same type" checking.

One stream of people prefer using if (! (other instanceof MyClass)), which have the shortcoming of

The other stream of people prefer using if (getClass() != other.getClass()), which has the shortcoming of


I have an idea in mind I would want people to verify.

This approach should

  1. If a child class overridden equals(), results should still be symmetric
  2. A child class will still be able to be used as its parent type (in aspect of equals(), and equals is not overridden of course)

    public class Parent {
        @Override
        public boolean equals(Object other) {
            if (other == null) return false;
            if (this == other) return true;
            if (!(other instanceof Parent)) return false;
            try {
                if (other.getClass().getMethod("equals", Object.class).getDeclaringClass() != Parent.class) {
                    return other.equals(this);
                }
            } catch(Exception neverHappen){}
    
            // do actual checking
        }
    }
    

The main idea is, if this encountered a Parent object, but that object's equals() is not declared in Parent (i.e. that object is a child class, with equals() overridden, I will delegate the equals invocation to the child class object (or may simply return false).

Is there any shortcoming of using this approach that I have overlooked? (I guess performance loss will be one concern, but I believe call of getClass().method("equals").getDeclaringClass() should be quite cheap?)

Upvotes: 2

Views: 5572

Answers (2)

Asoub
Asoub

Reputation: 2371

As stated in the comments, if the child class does super.equals(..), you would get a stackoverflow. To prevent this, you would end up rewriting the whole parents equals(..) in each children, which is even worst design.

Somehow, it depends on how/why you implemented inheritance in the first place. Should a child be comparable to a parent ? Does the comparaison makes sense ? (edited)

If you override equals, then you're not respecting LSP, by definition. If a child has more parameters (which means it overides the equals method), then it shouldn't be equal to its parent, and this is why we can compare using getClass() != other.getClass() in the parent. If you want to use the parent class's equals() in your child (so you don't have to rewrite it all), you wouldn't end up stackoverflowing; equals() would just be false, because they weren't meant to be equal.

What if a child is comparable to its parent ? If you're respecting LSP, the child shouldn't have a different equals() than his parent (ie: equals shouldn't be overriden), I guess. So the asymetric case shouldn't exists.

If a child is comparable to its parent, but has more parameters ? This is now your design, not respecting LSP, so it's up to you to see what meaning it does have in your context and act accordingly.


EDIT:@Adrian yes, "does symmmetry makes sense" was poor wording, I should have said "does the comparaison makes sense ?".

For your example, if you compare two child class with getClass() and they use super with also getClass() it will return true (the test will be redondant, because this.getClass() and other.getClass() will always have the same values, in the child and in the parent). But as I said, if you compare a child and a parent, it will be false (and I think it's normal if they have different parameters).

Why use final only on equals with instance of ? You said it, because of possible assymetry, whereas it's not possible to be assymetric with inheritance using getClass(), so it's no use making it final in this case.

As a side note, if you use getClass(), then multiple child of the same parent won't be comparable ( always return false ). If you use instanceof, they can, but if they do, no child should override equals() for risking of breaking symmetry. (I think you got this, but I was wondering when choose insteanceof instead of getClass() if it's so problematic).

Upvotes: 1

Peter Lawrey
Peter Lawrey

Reputation: 533660

Is there any shortcoming of using this approach?

It wouldn't work. The method to call is determined at compile time. Polymorphism only apply to reference before . not after it.

other.equals(this); // always calls equals(Object); as `other` is an Object.

Also

other.getClass().getMethod("equals", /* method with no args */).getDeclaringClass();

I assume you wanted other.getClass().getMethod("equals", Parent.class) but you would have to catch NoSuchMethodException

I believe call of getClass().method("equals").getDeclaringClass() should be quite cheap?

Not in my option. It's quicker than reading something from disk, but you wouldn't want to do this very often.

Last but not least you should ensure that a.equals(b) ==> b.equals(a) and if a.equals(b) then a.hashCode() == b.hashCode() which is hard to do is a and b are different types.

Upvotes: 0

Related Questions