It is possible to use Generic polymorphism, but not in the same way as Arrays
The reason you can not create a ArrayList<Cachorro>
object in a List<Animal>
reference is because it would be impossible for the JVM to prevent a ArrayList<Gato>
from being added to a ArrayList<Cachorro>
object. See 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 prevent 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
Because the reference variable is List<Animal>
, so the JVM is not able to prevent adding any subtype of animal to that list.
What is the problem of adding an object of type Gato
to the collection? None, until the moment you want 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 snippet will never generate a ClassCastException as long as the list is properly started with the use of Generics, like this:
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 is just adding elements that are not of the expected type to the collection, so Generic polymorphism can be used if :
1) Nothing is added to the collection
You can pass an object that is a subtype to a reference variable in a collection, as long as you do not 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 extremely inconvenient to make a mostrarNome()
method for each subtype of Animal
, correct? In addition, every time a new% new_document appears, a new method should be created, something that goes completely 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 Animal
snippet in the <? extends Animal>
parameter indicates that it is possible to pass subtype lists from mostrarNome()
to the Animal
reference variable and ensures nothing will be added to that list.
If you try to put the following code inside the animais
method:
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
2) Added something to Safe Mode 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 guarantee 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 mostrarNome()
excerpt says that the parameter accepts any argument that is a list of <? super Animal>
or any supertype of it. Instead of passing a list of Animal
we could have passed a list of Animal
which would also work:
List<Object> objetos = new ArrayList<Object>();
adicionarAnimais(objetos);
So, as long as you pass a list of Object
or any supertype of Animal
the Animal
method works well.
Why do not Arrays have such restrictions?
Unlike Arrays, they have a run-time exception: ArrayStoreException .
Generics do not exist at runtime, all programming using Generics is for compiler use only. So, there is no runtime protection for Generics, and in fact, it's NOT necessary! Since all the protection was done at compile time. (We are talking about Java 5 onwards, since before Java 5 there were no Generics.)
In other words, at runtime, the JVM knows what kind 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 throws an exception at runtime.