Suponha que eu tenha essa hierarquia de classes ...
public abstract class Animal {
public abstract void eat();
public abstract void talk();
}
class Dog extends Animal {
@Override
public void eat() {
}
@Override
public void talk() {
}
}
class Cat extends Animal {
@Override
public void eat() {
}
@Override
public void talk() {
}
}
E então eu tenho ....
public static <T extends Animal> void addAnimal(T animal) {
animal.eat();
animal.talk();
}
public static void addAnimalPoly(Animal animal) {
animal.eat();
animal.talk();
}
Qual é a diferença ao usar parâmetros de tipo limitado ou polimorfismo?
E quando usar um ou outro?
java
polymorphism
generics
HugoMelo
fonte
fonte
addAnimals(List<Animal>)
e adicionar uma lista de gatos!Respostas:
Esses dois exemplos são equivalentes e, de fato, serão compilados no mesmo bytecode.
Há duas maneiras pelas quais a adição de um tipo genérico limitado a um método, como no seu primeiro exemplo, fará qualquer coisa.
Passando o parâmetro type para outro tipo
Essas duas assinaturas de método acabam sendo as mesmas no código de bytes, mas o compilador impõe segurança de tipo:
public static <T extends Animal> void addAnimals(Collection<T> animals)
public static void addAnimals(Collection<Animal> animals)
No primeiro caso, apenas um
Collection
(ou subtipo) deAnimal
é permitido. No segundo caso, é permitido umCollection
(ou subtipo) com um tipo genéricoAnimal
ou um subtipo.Por exemplo, o seguinte é permitido no primeiro método, mas não no segundo:
O motivo é que o segundo permite apenas coleções de animais, enquanto o primeiro permite coleções de qualquer objeto atribuível ao animal (ou seja, subtipos). Observe que se essa lista fosse uma lista de animais que continham um gato, qualquer um dos métodos a aceitaria: o problema é a especificação genérica da coleção, não o que ela realmente contém.
Retornando objetos
A outra vez que importa é com objetos retornados. Vamos supor que o seguinte método existisse:
Você seria capaz de fazer o seguinte com ele:
Embora este seja um exemplo artificial, há casos em que faz sentido. Sem os genéricos, o método precisaria retornar
Animal
e você precisaria adicionar conversão de tipo para fazê-lo funcionar (que é o que o compilador adiciona ao código de bytes de qualquer maneira nos bastidores).fonte
Use genéricos em vez de fazer downcast. "Downcasting" é ruim, passando de um tipo mais geral para um mais específico:
... você confia que
a
é um gato, mas o compilador não pode garantir isso. Pode ser um cachorro em tempo de execução.Aqui é onde você usaria genéricos:
Agora você pode especificar que deseja um caçador de gatos:
Agora, o compilador pode gurantee que hunterC só irá capturar os gatos, e apenas os cães de captura hunterD.
Portanto, use polimorfismo regular se quiser lidar com classes específicas como seu tipo base. Upcasting é uma coisa boa. Mas se você entrar em uma situação em que precisa lidar com classes específicas como seu próprio tipo, use genericamente os genéricos.
Ou, realmente, se você achar que precisa fazer um downcast, use genéricos.
EDIT: o caso mais geral é quando você deseja adiar a decisão de quais tipos de tipos manipular. Portanto, os tipos se tornam um parâmetro, assim como os valores.
Digamos que eu queira que minha aula de zoológico lide com gatos ou esponjas. Eu não tenho uma super classe comum. Mas ainda posso usar:
o grau em que você bloqueia isso depende do que você está tentando fazer;)
fonte
Essa questão é antiga, mas um fator importante a ser considerado parece ter sido deixado de fora em relação a quando usar parâmetros de polimorfismo versus tipo limitado. Esse fator pode ser um pouco tangente ao exemplo dado na pergunta, mas eu acho muito relevante para os parâmetros mais gerais "Quando usar polimorfismo versus parâmetros do tipo delimitado?"
TL; DR
Se você se mudar do código de uma subclasse para uma classe base contra seu melhor julgamento, devido à incapacidade de acessá-lo de forma polimórfica, os parâmetros do tipo limitado podem ser uma solução potencial.
A resposta completa
Os parâmetros do tipo delimitado podem expor métodos de subclasse concretos e não herdados para uma variável de membro herdada. O polimorfismo não pode
Para elaborar estendendo seu exemplo:
Se a classe abstrata AnimalOwner tiver sido definida para ter um
protected Animal pet;
e optar pelo polimorfismo, o compilador apresentará um erro napet.scratchBelly();
linha, informando que esse método não está definido para Animal.fonte
No seu exemplo, você não (e não deve) usar o tipo delimitado. Use apenas parâmetros de tipo limitado quando for necessário , pois eles são mais confusos para entender.
Aqui estão algumas situações em que você usará parâmetros de tipo limitado:
Parâmetros de coleções
então você pode ligar
zoo.add(dogs)
falharia em compilar sem<? extends Animal>
, porque os genéricos não são covariantes.Subclassificação
para limitar o tipo que a subclasse pode fornecer.
Você também pode usar vários limites
<T extends A1 & A2 & A3>
para garantir que um tipo seja um subtipo de todos os tipos na lista.fonte