How to implement "equals" method for generics using "instanceof"?
Asked Answered
S

6

30

I have a class that accepts a generic type, and I want to override the equals method in a non-awkward way (i.e. something that looks clean and has minimal amount of code, but for a very general use case).

Right now I have something like this:

public class SingularNode<T> {
    private T value;

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object other){
        if(other instanceof SingularNode<?>){
            if(((SingularNode<T>)other).value.equals(value)){
                return true;
            }
        }
        return false;
    }
}

Which, I'm guessing, is pretty flawed - I'm doing a cast to SingularNode<T> on the other object, which could potentially throw an error.

Another thing is - when I do if(other instanceof SingularNode<?>) I'm actually not checking exactly the right thing. I actually want to check against type T and not type ?. Whenever I try to make the ? into T, I get some error like:

Cannot perform instanceof check against parameterized type SingularNode<T>. Use the form SingularNode<?> instead, since further generic type information will be erased at runtime

How can I get around this? Is there some way to do T.class.isInstance(other); ?

I suppose there's one really ugly hack solution like this:

@SuppressWarnings("unchecked")
public boolean isEqualTo(Class<?> c, Object obj){
    if(c.isInstance(obj) && c.isInstance(this)){
        if(((SingularNode<T>)obj).value.equals(value)){
            return true;
        }
    }
    return false;
}

But that just looks really awkward with the extra method parameter, and it's also not a built-in function like equals is.

Any one who understand generics please explain this? I'm not that proficient with Java, as you can clearly see, so please explain with a tad bit more detail!

Speckle answered 5/5, 2013 at 8:49 Comment(3)
The error you have is because anyway the generic types are erased. I think the "T" should take care of the check of whether passed element is of the same classExeter
@MichalBorek mmm could you please elaborate a bit? i'm not fully grasping. So in another class, if i do: new SingularNode<Integer>(5).equals(new SingularNode<Character>('k')); do you happen to know where the check is happening?Speckle
I added answer to put a bunch of code.Exeter
M
36

This version gives no warnings

public boolean equals(Object other){
    if (other instanceof SingularNode<?>){
        if ( ((SingularNode<?>)other).value.equals(value) ){
            return true;
        }
    }
    return false;
}

As for casting to SingularNode<T> it does not help anything, you cannot assume that T can be anything but Object.

Learn more about how generics are compiled in Java at

https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Myrmeco answered 5/5, 2013 at 9:23 Comment(9)
Thanks for the answer. Yes, that code will not give any warning because you are casting (SingularNode<?>)other up to type ? instead of T. however, i'd like to only use instances of type T in case that in the future I decide there's something special i wanted to do with type T. if that makes sense?Speckle
I dont think it makes sense since you cannot assume that T is anything but Object, cause it can be anything. Possibly it could be different in case of T extends SomethingMyrmeco
ah okay. So then actually ? is pretty much the same as T here?Speckle
Yes, just gives no warn, which is IMO is betterMyrmeco
@DavidT.: but why do you care that other might be a SingularNode<SomethingElse>?Adele
@Adele that was an error on my assumption. I had thought that in Java, bad stuff happens if the ? is not exactly T; and also later if i could do something with T, i dont want an error because i didn't type checkSpeckle
Sometimes you simply cannot avoid SuppressWarnings or using raw types. See JDK Collections.swap impl -> List l = list; in some cases even J.Bloch has to ignore generics and use raw typesMyrmeco
This solution lets the T's determine if they are equal, which is right IMHO. But change the if (condition) return true to return ((SingularNode<?>)other).value.equals(value);.Spiral
No return of false if object type matches but not value. public boolean equals(Object other){ if (other instanceof SingularNode<?>){ if(this.value != null) return this.value.equals(((SingleNode<?>) other).value); else return ((SingleNode<?>)other).value == null; } return false; }Gabrielson
T
8

Evgeniy's solution and Michal's reasoning are correct - you don't need to worry about the type of T here. The reason is that the equals method doesn't depend on generics to work correctly. Instead, it is declared by Object and it takes an Object. Thus, it's responsible for checking the runtime type of whatever was passed in.

If this happens to be SingularNode<String> and you compare it with a SingularNode<Integer>, then ((SingularNode<?>)other).value.equals(value) is perfectly fine because calling Integer.equals with a String argument will correctly return false.

Thallium answered 5/5, 2013 at 16:8 Comment(0)
E
1

I put answer here to put code..

In your example you have (in pseudo code) Integer(5).equals(Char('k')) which is false, according to following equals implementation on java.lang.Integer:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

Going that way you don't have to worry about casting.

Exeter answered 5/5, 2013 at 9:18 Comment(4)
Oh okay, i see what you mean now. thank you. For some reason, I was under wrong the impression that Java throws an InvalidClassCast exception or something when you try to cast Character with an Integer. but it just returns false instead of erroring. i see it nowSpeckle
Usually the first think we check in equals method is class of the other object, and all Java equals implementations do it that way.Exeter
So in the cast (SingularNode<T>)obj, what's actually happening is that it is casting obj to SingularNode, rather than T. which means that the obj.value is actually type T, right?Speckle
Yes. Precisely it is of type java.lang.Object, since after compilation generics are removed. Generics are needed only at compile time to check for type safety (this is called type erasure). So generics are not the same as templates in C++Exeter
T
1

I have the same problem, however it is more general. I have a class where I do have 3 generic types. I do not need to store any variable of those types because this class is used for transformation. However there are stored 'request' variables and I do use cache based on this class, so I need to implement equals() method that is based on those generics.

Do you know if there is any approach how to do it without reflection? Maybe internal variable of that type.. However it's null then.

public class TheClass<I, O, M> {

    private ClassA param1;
    private ClassB param2;
    private ClassC<M> param3;
    private BiFunction<ClassC<M>, I, Optional<O>> mapping;

    public ClassD<O, M> doSomething(ClassD<I, M> param) {
        ...
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if (getClass() != o.getClass()) {
            return false;
        }
        TheClass<?, ?, ?> that = (TheClass<?, ?, ?>) o;

        return Objects.equals(getParam1(), that.getParam1()) &&
                Objects.equals(getParam2(), that.getParam2()) &&
                Objects.equals(getParam3(), that.getParam3());
    }
}

For a better imagination... I have set of DAO objects getting data from database. On the other hand, we do have another set of API providers that provide similar data in different format (REST, internal systems..) We need a mapping function from one type to another. We use caching for better performance and the only man-in-the-middle is this class.

Turgeon answered 26/1, 2018 at 16:32 Comment(1)
Should have posted it in a separate question. But it actually looks reasonable enough. getClass() != o.getClass() is equivalent to !(o instanceof TheClass<?,?,?>) or, with warnings, to !(o instanceof TheClass).Creeps
V
0

This is how HashMap handles it :

        public final boolean equals(Object o) {
            if (o == this)
                return true;

            return o instanceof Map.Entry<?, ?> e
                    && Objects.equals(key, e.getKey())
                    && Objects.equals(value, e.getValue());
        }
Vase answered 23/11, 2023 at 14:36 Comment(0)
G
-5

You dont need to use any casting. Best equals to implementation I see like this

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof VehicleModel)) return false;

        VehicleModel that = (VehicleModel) o;

        if (vehicleName != null ? !vehicleName.equals(that.vehicleName) : that.vehicleName != null)
            return false;

        return true;
    }
Glut answered 22/10, 2013 at 15:52 Comment(1)
I'm confused. I see casting.Pre

© 2022 - 2024 — McMap. All rights reserved.