Substituindo métodos passando como argumento o objeto da subclasse em que o supertipo é esperado


12

Estou apenas aprendendo Java e não sou um programador praticante.

O livro que estou seguindo diz que, ao substituir um método, os tipos de argumento devem ser os mesmos, mas os tipos de retorno podem ser polimorficamente compatíveis.

Minha pergunta é por que os argumentos passados ​​para o método de substituição não podem ser um tipo de subclasse do super-tipo esperado?

No método sobrecarregado, qualquer método que eu chamo no objeto tem a garantia de ser definido no objeto.


Notas sobre duplicatas sugeridas:

A primeira sugestão parece ser sobre hierarquia de classes e onde colocar a funcionalidade. Minha pergunta é mais focada em por que a restrição de idioma existe.

A segunda sugestão explica como fazer o que estou pedindo, mas não por que isso deve ser feito dessa maneira. Minha pergunta está focada no porquê.


1
perguntou e respondeu na pergunta anterior : "Isso geralmente é considerado como falha no Princípio de Substituição de Liskov (LSP), porque a restrição adicional faz com que as operações da classe base nem sempre sejam apropriadas para a classe derivada ..."
gnat


@gnat a pergunta não é se a exigência de subtipos mais específicos para argumentos viola algum princípio do computador; a questão é se é possível, que edalorzo respondeu.
user949300

Respostas:


18

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 Sfor um subtipo de T, então, onde quer que Tapareça, podemos passar um S.

Como tal, é seguro retornar um Sao 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, AnimalHuntervocê deve ser capaz de caçar qualquer animal, mas de acordo com sua implementação, MammutHuntervocê só aceita Mammutobjetos. 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 MammalHunterinstrumentos 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.


3

O problema não é o que você faz no método de substituição com o objeto fornecido como argumento. O problema é qual é o tipo de argumento que o código usando o seu método pode alimentar ao seu método.

Um método de substituição deve cumprir o contrato do método de substituição. Se o método substituído aceitar argumentos de alguma classe e você o substituir por um método que aceita apenas uma subclasse, ele não cumprirá o contrato. É por isso que não é válido.

Substituir um método por um tipo mais específico de argumento é chamado de sobrecarga . Ele define um método com o mesmo nome, mas com um tipo de argumento diferente ou mais específico. Um método sobrecarregado está disponível além do método original. Qual método é chamado depende do tipo conhecido em tempo de compilação. Para sobrecarregar um método, você deve descartar a anotação @Override.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.