It is possible to make use of polymorphism with Generics, but not in the same way as with Arrays
The reason you cannot create an object ArrayList<Cachorro>
in a reference List<Animal>
is why it would be impossible for the JVM to avoid adding a ArrayList<Gato>
in an object ArrayList<Cachorro>
. Take the example:
//suponhamos que fosse possível fazer o que a linha abaixo sugere
List<Animal> cachorros = new ArrayList<Cachorro>(); //apenas suponha, essa linha não compila!
Nothing would stop you from doing this on subsequent lines:
cachorros.add(new Cachorro()); //OK
cachorros.add(new Gato())); //ops! adicionou um Gato em uma lista de Cachorros
For the referencing variable is a List<Animal>
, so the JVM is not able to prevent adding any animal subtype to that list.
What’s the problem of adding an object of the type Gato
in the collection? None, until the moment you wish to read the collection and treat its elements as Cachorro
s.
for(Cachorro c: cachorros) { //gera um ClassCastException se ler um objeto de Gato
}
Generics serve to make the code safer and easier to read, so the above section will never generate a Classcastexception provided that the list is properly started with the use of Generics, thus:
List<Cachorro> cachorros = new ArrayList<Cachorro>();
The above code ensures that nothing other than Cachorro
, or its subtypes, will be added to the collection.
How to use Polymorphism and Generics then?
The problem lies only in adding elements that are not of the expected type to the collection, so, use of polymorphism with Generic can be used if:
1) Nothing added to the collection
You can pass an object that is a subtype to a reference variable from a collection, as long as you don’t add anything to it.
Example:
import java.util.*;
class Animal {
private String nome;
Animal(String nome) { this.nome = nome; }
public String getNome() { return nome; }
}
class Cachorro extends Animal {
Cachorro(String nome) { super(nome); }
}
class Gato extends Animal {
Gato(String nome) { super(nome); }
}
public class Teste{
public static void main(String[] args) {
List<Gato> gatos = new ArrayList<Gato>();
List<Cachorro> cachorros = new ArrayList<Cachorro>();
gatos.add(new Gato("Gray"));
gatos.add(new Gato("Brown"));
cachorros.add(new Cachorro("Pim"));
mostrarNome(gatos); //chama o método polimorficamente
mostrarNome(cachorros); //chama o método polimorficamente
}
//método polimórfico para mostrar nome
}
It would be something extremely inconvenient to make a method mostrarNome()
for each subtype of Animal
, right? Plus, every time a Animal
a new method should be created, something that goes totally against the principles of object orientation.
But there is a solution:
//método polimórfico para mostrar nome
public static void mostrarNome(List<? extends Animal> animais){
for(Animal a: animais) {
System.out.println("Me chamo: " + a.getNome());
}
}
The excerpt <? extends Animal>
in the method parameter mostrarNome()
indicates that lists of subtypes of Animal
for the reference variable animais
and ensures that nothing will be added to that list.
If you try to put the following code inside the method mostrarNome()
:
animais.add(new Cachorro("Tobi"));
The compiler will return the following error:
The method add(capture#2-of ? extends Animal) in the type List is not applicable for the Arguments (Puppy)
2) Safely add something to the collection
Eventually you may find yourself in a situation where you need to add objects to the collection.
Example:
public class Teste{
public static void main(String[] args) {
//chama o método polimorficamente
List<Animal> animais = new ArrayList<Animal>();
adicionarAnimais(animais); //chama o método polimorficamente
}
//método polimórfico que adiciona animais
}
You can add, as long as you ensure to the compiler that the collection is supertype of the object you want to add.
//método polimórfico que adiciona animais
public static void adicionarAnimais(List<? super Animal> animais) {
animais.add(new Cachorro());
animais.add(new Gato());
animais.add(new Papagaio());
}
The excerpt <? super Animal>
says that the parameter accepts any argument that is a list of Animal
or any supertype of it. Instead of passing a list of Animal
we could have passed a list of Object
that would also work:
List<Object> objetos = new ArrayList<Object>();
adicionarAnimais(objetos);
So long as you pass a list of Animal
or any supertype of Animal
the method adicionarAnimais()
works well.
Why with Arrays there are no such restrictions?
The difference between Arrays is that they have an exception at runtime: Arraystoreexception.
Generics do not exist at runtime, all programming that uses Generics is for the exclusive use of the compiler. Therefore, there is no runtime protection for Generics, and indeed, it is NOT necessary! Since all protection was done at compile time. (We’re talking about Java 5 onwards, since before Java 5 there were no Generics).
In other words, at runtime, the JVM knows the type of arrays, but does not know the type of a collection.
To illustrate:
class Animal { }
class Cachorro extends Animal { }
class Gato extends Animal { }
public class TesteArray {
public static void main(String[] args) {
Animal[] cachorros = new Cachorro[10];
cachorros[0] = new Cachorro();
cachorros[1] = new Gato();
}
}
Compile, but generate an exception at runtime.
The question is mistaken because it is possible to use generic polymorphism, but I have to disagree with your point of view because you pointed out a case of polymorphism with
Collection
and not withGenéricos
. In case the question should have been:Por que o polimorfismo não funciona com **Collection**?
. You said it yourself:List cachorros = new ArrayList(); é polimórfico em relação as **coleções** List / ArraList
.– Math
Already his method
public boolean add(Animal a)
seems to be a good alternative to use polymorphism with Generic Collection && .– Math