web-dev-qa-db-fra.com

Comment implémenter la méthode "equals" pour les génériques en utilisant "instanceof"?

J'ai une classe qui accepte un type générique , et je veux remplacer la méthode equals de manière non gênante (c'est-à-dire quelque chose qui a l'air propre et contient un minimum de code, mais pour un cas d'utilisation très général ).

En ce moment j'ai quelque chose comme ça:

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;
    }
}

Ce qui, je suppose, est assez imparfait - je fais un transtypage en SingularNode<T> sur l'objet other, ce qui pourrait potentiellement renvoyer une erreur .

Une autre chose est - quand je fais if(other instanceof SingularNode<?>) je ne vérifie pas exactement la bonne chose. En fait, je veux vérifier le type T et non le ?. Chaque fois que j'essaie de transformer le ? en T, des erreurs telles que:

Impossible d'effectuer une vérification du type paramétré SingularNode<T>. Utilisez plutôt le formulaire SingularNode<?>, car d'autres informations de type génériques seront effacées lors de l'exécution

Comment puis-je contourner cela? Y a-t-il un moyen de faire T.class.isInstance(other);?

Je suppose qu’il existe une solution vraiment moche comme celle-ci:

@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;
}

Mais cela semble vraiment gênant avec le paramètre supplémentaire de la méthode, et ce n'est pas non plus une fonction intégrée comme celle de equals.

Quelqu'un qui comprend les génériques s'il vous plaît expliquer cela? Je ne suis pas très compétent en Java, comme vous pouvez le constater, veuillez donc expliquer un peu plus en détail!

19
David T.

Cette version ne donne pas d'avertissement

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

En ce qui concerne le transtypage en SingularNode<T> cela ne sert à rien, vous ne pouvez pas supposer que T peut être autre chose que Object

En savoir plus sur la façon dont les génériques sont compilés en Java à l'adresse

https://docs.Oracle.com/javase/tutorial/Java/generics/erasure.html

21

La solution d'Evgeniy et Le raisonnement de Michal sont corrects - vous n'avez pas à vous soucier du type de T ici. La raison en est que la méthode equals ne dépend pas des génériques pour fonctionner correctement. Au lieu de cela, il est déclaré par Object et prend une Object. Ainsi, il est responsable de vérifier le type d'exécution de tout ce qui a été transmis.

Si this se trouve être SingularNode<String> et que vous le comparez à un SingularNode<Integer>, alors ((SingularNode<?>)other).value.equals(value) convient parfaitement, car appeler Integer.equals avec un argument String retournera correctement false.

5
Paul Bellora

Je mets réponse ici pour mettre le code ..

Dans votre exemple, vous avez (dans le pseudo-code) Integer(5).equals(Char('k')) qui est false, conformément à l'implémentation suivante de Java.lang.Integer:

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

En procédant de cette façon, vous n'avez pas à vous soucier du casting.

1
Michal Borek

J'ai le même problème, mais c'est plus général. J'ai une classe où j'ai 3 types génériques. Je n'ai besoin de stocker aucune variable de ces types car cette classe est utilisée pour la transformation. Cependant, il existe des variables de 'requête' stockées et j'utilise le cache basé sur cette classe. Je dois donc implémenter la méthode equals () basée sur ces génériques. 

Savez-vous s'il existe une approche comment le faire sans réflexion? Peut-être une variable interne de ce type .. Cependant, elle est nulle alors. 

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());
    }
}

Pour une meilleure imagination ... J'ai ensemble d'objets DAO récupérant des données de la base de données. D'autre part, nous avons un autre ensemble de fournisseurs d'API qui fournissent des données similaires dans un format différent (REST, systèmes internes, etc.) Nous avons besoin d'une fonction de mappage d'un type à un autre. Nous utilisons la mise en cache pour de meilleures performances et le seul homme du milieu est cette classe.

0
JiangHongTiao