Substitution de méthodes en passant comme argument l'objet de sous-classe où le supertype est attendu

12

J'apprends juste Java et je ne suis pas un programmeur pratiquant.

Le livre que je suis en train de dire dit que lors du remplacement d'une méthode, les types d'arguments doivent être les mêmes, mais les types de retour peuvent être polymorphiquement compatibles.

Ma question est pourquoi les arguments passés à la méthode prioritaire ne peuvent-ils pas être un type de sous-classe du super-type attendu?

Dans la méthode surchargée, la méthode que j'appelle sur l'objet est garantie d'être définie sur l'objet.


Remarques sur les doublons suggérés:

La première suggestion semble concerner la hiérarchie des classes et l'emplacement des fonctionnalités. Ma question est plus axée sur la raison pour laquelle la restriction linguistique existe.

La deuxième suggestion explique comment faire ce que je demande, mais pas pourquoi cela doit être fait de cette façon. Ma question porte sur le pourquoi.

user1720897
la source
1
demandé et répondu dans la question précédente : "Ceci est généralement considéré comme un échec du principe de substitution de Liskov (LSP), car la contrainte supplémentaire fait que les opérations de classe de base ne sont pas toujours appropriées pour la classe dérivée ..."
gnat
@gnat la question n'est pas de savoir si exiger des sous-types plus spécifiques pour les arguments viole un principe informatique, la question est de savoir si c'est possible, ce à quoi edalorzo a répondu.
user949300

Réponses:

18

Le concept auquel vous faites initialement référence dans votre question est appelé type de retour covariant .

Les types de retour covariants fonctionnent car une méthode est censée renvoyer un objet d'un certain type et les méthodes de substitution peuvent en renvoyer une sous-classe. Sur la base des règles de sous-typage d'un langage comme Java, s'il Ss'agit d'un sous-type de T, alors partout où Tapparaît, nous pouvons passer un S.

En tant que tel, il est sûr de retourner un Slors de la substitution d'une méthode qui attendait a T.

Votre suggestion d'accepter qu'une substitution d'une méthode utilise des arguments qui sont des sous-types de ceux demandés par la méthode substituée est beaucoup plus compliquée car elle conduit à des anomalies dans le système de types.

D'une part, selon les mêmes règles de sous-typage mentionnées ci-dessus, il est très probable que cela fonctionne déjà pour ce que vous voulez faire. Par exemple

interface Hunter {
   public void hunt(Animal animal);
}

Rien n'empêche les implémentations de cette classe de recevoir tout type d'animal, en tant que tel il répond déjà aux critères de votre question.

Mais supposons que nous pourrions remplacer cette méthode comme vous l'avez suggéré:

class MammutHunter implements Hunter {
  @Override
  public void hunt(Mammut animal) {
  }
}

Voici la partie amusante, maintenant vous pouvez le faire:

AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh

Selon l'interface publique de AnimalHuntervous, vous devriez pouvoir chasser n'importe quel animal, mais selon votre implémentation, MammutHuntervous n'acceptez que des Mammutobjets. Par conséquent, la méthode remplacée ne satisfait pas l'interface publique. Nous venons de rompre la solidité du système de type ici.

Vous pouvez implémenter ce que vous voulez en utilisant des génériques.

interface AnimalHunter<T extends Animal> {
   void hunt(T animal);
}

Ensuite, vous pouvez définir votre MammutHunter

class MammutHunter implements AnimalHunter<Mammut> {
   void hunt(Mammut m){
   }
}

Et en utilisant la covariance et la contravariance génériques, vous pouvez assouplir les règles en votre faveur si nécessaire. Par exemple, nous pourrions nous assurer qu'un chasseur de mammifères ne peut chasser des félins que dans un contexte donné:

AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());

Supposons des MammalHunteroutils AnimalHunter<Mammal>.

Dans ce cas, cela ne serait pas accepté:

hunter.hunt(new Mammut()):

Même lorsque les mammuts sont des mammifères, cela ne serait pas accepté en raison des restrictions sur le type contravariant que nous utilisons ici. Donc, vous pouvez toujours exercer un certain contrôle sur les types pour faire des choses comme celles que vous avez mentionnées.

edalorzo
la source
3

Le problème n'est pas ce que vous faites dans la méthode prioritaire avec l'objet donné en argument. Le problème est quel est le type d'arguments que le code utilisant votre méthode est autorisé à alimenter à votre méthode.

Une méthode prioritaire doit respecter le contrat de la méthode substituée. Si la méthode substituée accepte des arguments d'une classe et que vous la remplacez par une méthode qui n'accepte qu'une sous-classe, elle ne remplit pas le contrat. C'est pourquoi il n'est pas valable.

La surcharge d'une méthode avec un type d'argument plus spécifique est appelée surcharge . Il définit une méthode avec le même nom mais avec un type d'argument différent ou plus spécifique. Une méthode surchargée est disponible en plus de la méthode d'origine. La méthode appelée dépend du type connu au moment de la compilation. Pour surcharger une méthode, vous devez supprimer l'annotation @Override.

Florian F
la source