web-dev-qa-db-fra.com

toString (), equals () et hashCode () dans une interface

Donc, j'ai une interface avec un tas de méthodes qui doivent être implémentées, les noms de méthode ne sont pas pertinents.

Les objets qui implémentent cette interface sont souvent placés dans des collections, et ont également un format toString () spécial que je souhaite qu'ils utilisent.

Donc, j'ai pensé qu'il serait pratique de mettre hashCode (), equals () et toString () dans l'interface, pour m'assurer de ne pas oublier de remplacer la méthode par défaut pour ceux-ci. Mais quand j'ai ajouté ces méthodes à l'interface, l'IDE/Compilateur ne se plaint pas si je n'ai pas ces trois méthodes implémentées, même si je les ai explicitement mises dans l'interface.

Pourquoi cela ne sera-t-il pas appliqué pour moi? Il se plaint si je n'implémente aucune des autres méthodes, mais il n'applique pas ces trois. Ce qui donne? Des indices?

51
J_Y_C

Tous les objets de Java héritent de Java.lang.Object Et Object fournit des implémentations par défaut de ces méthodes.

Si votre interface contient d'autres méthodes, Java se plaindra si vous n'implémentez pas complètement l'interface en fournissant une implémentation de ces méthodes. Mais dans le cas de equals(), hashCode() et toString() (ainsi que quelques autres que vous n'avez pas mentionnés) l'implémentation existe déjà.

Vous pouvez peut-être accomplir ce que vous voulez en fournissant une méthode différente dans l'interface, par exemple toPrettyString() ou quelque chose comme ça. Ensuite, vous pouvez appeler cette méthode au lieu de la méthode par défaut toString().

38
Adam Batkin

Il semble que vous souhaitiez forcer vos classes pour remplacer les implémentations par défaut de ces méthodes. Si tel est le cas, la façon de procéder consiste à déclarer une superclasse abstraite dont les méthodes sont déclarées abstraites. Par exemple:

public abstract class MyBaseClass implements ... /* existing interface(s) */ {

    public abstract boolean equals(Object other);

    public abstract int hashCode();

    public abstract String toString();
}

Modifiez ensuite vos classes actuelles en extend cette classe.

Cette approche fonctionne, mais ce n'est pas une solution idéale.

  • Cela peut être problématique pour une hiérarchie de classes existante.

  • C'est une mauvaise idée de forcer les classes qui implémentent votre interface existante pour étendre une classe abstraite spécifique. Par exemple, vous pouvez modifier les paramètres des signatures de méthode pour utiliser la classe abstraite plutôt que les interfaces existantes. Mais le résultat final est un code moins flexible. (Et les gens peuvent trouver des moyens de renverser cela de toute façon; par exemple, en ajoutant leur propre sous-classe abstraite qui "implémente" les méthodes avec un appel super.<method>(...)!)

  • L'imposition d'une hiérarchie de classe/d'un modèle d'implémentation particulier est à courte vue. Vous ne pouvez pas prédire si une modification future des exigences entraînera des difficultés pour vos restrictions. (C'est pourquoi les gens recommandent de programmer contre des interfaces plutôt que des classes spécifiques.)


Revenons à votre question actuelle sur la raison pour laquelle votre interface ne force pas une classe à redéclarer ces méthodes:

Pourquoi cela ne sera-t-il pas appliqué pour moi? Il se plaint si je n'implémente aucune des autres méthodes, mais il n'applique pas ces trois. Ce qui donne? Des indices?

Une interface impose la contrainte qu'une classe concrète l'implémentant a une implémentation pour chacune des méthodes. Cependant, il ne nécessite pas que la classe elle-même implémente ces méthodes. Les implémentations de méthode peuvent être héritées d'une superclasse. Et dans ce cas, c'est ce qui se passe. Les méthodes héritées de Java.lang.Object Satisfont à la contrainte.

JLS 8.1.5 indique ce qui suit:

"Sauf si la classe déclarée est abstraite, toutes les méthodes membres abstraites de chaque superinterface directe doivent être implémentées (§8.4.8.1) soit par une déclaration dans cette classe soit par un déclaration de méthode existante héritée de la superclasse directe ou d'une superinterface directe , car une classe qui n'est pas abstraite n'est pas autorisée à avoir des méthodes abstraites (§8.1.1.1). "

39
Stephen C

Ces 3 méthodes sont définies par Java.lang.Object qui est (implicitement) étendu par toutes les autres classes; par conséquent, les implémentations par défaut de ces méthodes existent et le compilateur n'a rien à redire.

17
ChssPly76

Toute classe qui implémente votre interface étend également Object. L'objet définit hashCode, equals et toString et possède des implémentations par défaut des trois.

Ce que vous essayez de réaliser est bon, mais pas réalisable.

7
Steve McLeod

Il existe une implémentation pour ces méthodes provenant de Object.

5
Tordek

Si vous souhaitez forcer la substitution de equals () et hashCode (), étendez-vous à partir d'une superclasse abstraite, qui définit ces méthodes comme abstraites.

5
Bozho

Votre objet contient déjà des implémentations de ces trois méthodes, car chaque objet hérite de ces méthodes d'Object, sauf si elles sont remplacées.

4
Matt Ball

Java se soucie seulement que les méthodes sont définies quelque part. L'interface ne vous oblige pas à redéfinir les méthodes dans les nouvelles classes qui héritent de l'interface pour la première fois si elles sont déjà définies. Puisque Java.lang.Object implémente déjà ces méthodes, vos nouveaux objets sont conformes à l'interface même s'ils ne remplacent pas ces trois méthodes par eux-mêmes.

3
Ken Bloom

D'autres personnes ont suffisamment répondu à votre question. En ce qui concerne une solution à votre problème particulier, vous pouvez envisager de créer vos propres méthodes (peut-être getStringRepresentation, getCustomHashcode et equalsObject) et demander à vos objets d'étendre une classe de base dont les méthodes equals, toString et hashCode appellent ces méthodes.

Cela pourrait cependant aller à l'encontre de l'objectif d'utiliser une interface en premier lieu. C'est l'une des raisons pour lesquelles certaines personnes ont suggéré que equals, toString et hashCode n'auraient jamais dû être inclus dans la classe Object en premier lieu.

3
StriplingWarrior

Adam vous donne la raison pour laquelle vous ne pouvez pas vous en sortir en essayant de forcer equals, hashCode et toString. J'irais pour la mise en œuvre suivante qui touche la solution fournie par Adam et Stephan:

public interface Distinct {
    boolean checkEquals(Object other);
    int hash();
}

public interface Stringable {
    String asString();
}

public abstract class DistinctStringableObject {

    @Override
    public final boolean equals(Object other) {
        return checkEquals();
    }

    @Override
    public final int hashCode() {
        return hash();
    }

    @Override
    public final String toString() {
        return asString();
    }
}

Désormais, toute classe qui doit se distinguer et être représentée comme une chaîne peut étendre DistinctStringableObject, ce qui forcera l'implémentation de checkEquals, hashCode et toString.

Exemple de classe de béton:

public abstract class MyDistinctStringableObject extends DistinctStringableObject {

    @Override
    public final boolean checkEquals(Object other) {
        ...
    }

    @Override
    public final int hash() {
        ...
    }

    @Override
    public final String asString() {
        ...
    }
}
1
Neel

Les classes abstraites ne fonctionneront pas si vous avez un petit-enfant, car son père a déjà remplacé les méthodes égal et hashCode et vous avez à nouveau votre problème.

Essayez d'utiliser des annotatins et APT ( http://docs.Oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html ) pour obtenir c'est fait.

0
Philippe Gioseffi