O conceito a que você se refere inicialmente na sua pergunta é chamado de tipos de retorno covariante .
Os tipos de retorno covariantes funcionam porque é suposto que um método retorne um objeto de determinado tipo e os métodos de substituição podem realmente retornar uma subclasse dele. Com base nas regras de subtipagem de uma linguagem como Java, se S
for um subtipo de T
, então, onde quer que T
apareça, podemos passar um S
.
Como tal, é seguro retornar um S
ao substituir um método que esperava a T
.
Sua sugestão de aceitar que uma substituição de um método use argumentos que são subtipos daqueles solicitados pelo método substituído é muito mais complicada, pois leva à insatisfação no sistema de tipos.
Por um lado, pelas mesmas regras de subtipo mencionadas acima, provavelmente já funciona para o que você deseja fazer. Por exemplo
interface Hunter {
public void hunt(Animal animal);
}
Nada impede que as implementações desta classe recebam qualquer tipo de animal, pois, como tal, ele já atende aos critérios de sua pergunta.
Mas vamos supor que possamos substituir esse método como você sugeriu:
class MammutHunter implements Hunter {
@Override
public void hunt(Mammut animal) {
}
}
Aqui está a parte engraçada, agora você pode fazer isso:
AnimalHunter hunter = new MammutHunter();
hunter.hunt(new Bear()); //Uh oh
De acordo com a interface pública, AnimalHunter
você deve ser capaz de caçar qualquer animal, mas de acordo com sua implementação, MammutHunter
você só aceita Mammut
objetos. Portanto, o método substituído não satisfaz a interface pública. Acabamos de quebrar a solidez do sistema de tipos aqui.
Você pode implementar o que deseja usando genéricos.
interface AnimalHunter<T extends Animal> {
void hunt(T animal);
}
Então você pode definir seu MammutHunter
class MammutHunter implements AnimalHunter<Mammut> {
void hunt(Mammut m){
}
}
E usando covariância e contravariância genéricas, você pode relaxar as regras a seu favor quando necessário. Por exemplo, podemos garantir que um caçador de mamíferos só possa caçar felinos em um determinado contexto:
AnimalHunter<? super Feline> hunter = new MammalHunter();
hunter.hunt(new Lion());
hunter.hunt(new Puma());
Supondo MammalHunter
instrumentos AnimalHunter<Mammal>
.
Nesse caso, isso não seria aceito:
hunter.hunt(new Mammut()):
Mesmo quando mamutes são mamíferos, isso não seria aceito devido às restrições do tipo contravariante que estamos usando aqui. Portanto, você ainda pode exercer algum controle sobre os tipos para fazer coisas como as que você mencionou.