J'apprends juste Java et je ne suis pas un programmeur pratiquant.
Le livre que je suis en train de dire dit que lors de la substitution 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:
première suggestion semble concerner la hiérarchie des classes et où placer les fonctionnalités. Ma question est plus axée sur la raison pour laquelle la restriction linguistique existe.
Le 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.
Le concept auquel vous faites référence initialement dans votre question s'appelle types de retour covariants .
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. Basé sur les règles de sous-typage d'un langage comme Java, si S
est un sous-type de T
, alors partout où T
apparaît, nous pouvons passer un S
.
En tant que tel, il est sûr de retourner un S
lors de la substitution d'une méthode qui attendait un 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, vous pouvez maintenant le faire:
AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh
Selon l'interface publique de AnimalHunter
, vous devriez pouvoir chasser n'importe quel animal, mais selon votre implémentation de MammutHunter
, vous n'acceptez que les objets Mammut
. 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 que MammalHunter
implémente 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. Ainsi, vous pouvez toujours exercer un certain contrôle sur les types pour faire des choses comme celles que vous avez mentionnées.
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 dans 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.
Remplacer une méthode avec un type d'argument plus spécifique s'appelle 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.