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 S
s'agit d'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 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 AnimalHunter
vous, vous devriez pouvoir chasser n'importe quel animal, mais selon votre implémentation, MammutHunter
vous n'acceptez que des Mammut
objets. 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 MammalHunter
outils 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.