El concepto al que se refiere inicialmente en su pregunta se llama tipos de retorno covariante .
Los tipos de retorno covariantes funcionan porque se supone que un método devuelve un objeto de cierto tipo y los métodos de anulación en realidad pueden devolver una subclase del mismo. Según las reglas de subtipo de un lenguaje como Java, si S
es un subtipo de T
, entonces, donde sea que T
aparezca, podemos pasar un S
.
Como tal, es seguro devolver un S
al anular un método que esperaba a T
.
Su sugerencia de aceptar que una anulación de un método utiliza argumentos que son subtipos de los solicitados por el método anulado es mucho más complicada, ya que conduce a la falta de solidez en el sistema de tipos.
Por un lado, según las mismas reglas de subtipo mencionadas anteriormente, lo más probable es que ya funcione para lo que desea hacer. Por ejemplo
interface Hunter {
public void hunt(Animal animal);
}
Nada impide que las implementaciones de esta clase reciban ningún tipo de animal, por lo que ya satisface los criterios de su pregunta.
Pero supongamos que podríamos anular este método como usted sugirió:
class MammutHunter implements Hunter {
@Override
public void hunt(Mammut animal) {
}
}
Aquí está la parte divertida, ahora puedes hacer esto:
AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh
Según su interfaz pública AnimalHunter
, debería poder cazar cualquier animal, pero según su implementación MammutHunter
, solo acepta Mammut
objetos. Por lo tanto, el método reemplazado no satisface la interfaz pública. Acabamos de romper la solidez del sistema de tipos aquí.
Puedes implementar lo que quieras usando genéricos.
interface AnimalHunter<T extends Animal> {
void hunt(T animal);
}
Entonces podrías definir tu MammutHunter
class MammutHunter implements AnimalHunter<Mammut> {
void hunt(Mammut m){
}
}
Y utilizando la covarianza y la contravarianza genéricas, puede relajar las reglas a su favor cuando sea necesario. Por ejemplo, podríamos asegurarnos de que un cazador de mamíferos solo pueda cazar felinos en un contexto dado:
AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());
Suponiendo MammalHunter
implementos AnimalHunter<Mammal>
.
En ese caso, esto no sería aceptado:
hunter.hunt(new Mammut()):
Incluso cuando los mamuts son mamíferos, no sería aceptado debido a las restricciones sobre el tipo contravariante que estamos usando aquí. Por lo tanto, aún puede ejercer algún control sobre los tipos para hacer cosas como las que mencionó.