Why doesn’t polymorphism work with Generics?

Asked

Viewed 964 times

17

When trying to compile the following code I got an error.

import java.util.*;
class Animal { }
class Cachorro extends Animal { }
public class TestePoli {
    public static void main(String[] args) {
        List<Animal> cachorros = new ArrayList<Cachorro>();
    }
}

Type Mismatch: cannot Convert from Arraylist to List

The following example also makes use of polymorphism however without the use of Generics, and works perfectly:

class Animal { }
class Cachorro extends Animal { }
public class TestePoli {
    public static void main(String[] args) {
        Animal cachorro = new Cachorro();      //polimorfismo
        Animal[] cachorros = new Cachorro[10]; //polimorfismo com Arrays
    }
}

Even in this example we use polymorphism with Arrays, which would be something similar to List, and runs smoothly.

Why it is not possible to make use of polymorphism with Generics?

2 answers

20


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 Cachorros.

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.

3

There is an interpretation error in your question when you say "Why doesn’t polymorphism work with Generics?" because the code List dogs = new Arraylist(); is polymorphic in relation to the collections List / Arralist. It could actually be any type in the java.util Collection inheritance chain that would compile.

The problem in my view is another:

The line below compiles

Animal[] cachorros = new Cachorro[10]; //polimorfismo com Arrays

But it’s not safe because

cachorros[0] = new Gato("miau");

also compiles but is semantically wrong. Error will occur at runtime of type Classcastexception

The approach with Generic types is safer, but the programmer is responsible for creating a mechanism that ensures that there is no incorrect handling of type, which in this example below is guaranteed by the method add class ListaCachorro. In real life we will use an interface but I simplified the example for didactic reasons.

import java.util.ArrayList;

public abstract class Animal {

    public static void main(String[] args) {
        ListaCachorro cachorros = new ListaCachorro();
        cachorros.add(new Cachorro("Rex", "au au"));
    }
}

class Cachorro extends Animal {
    public Cachorro(String nome, String latido) {
    }
}

class Gato extends Animal {
    public Gato(String nome, String miado) {
    }
}

class ListaCachorro extends ArrayList<Animal> {
    @Override
    public boolean add(Animal a) {
        // seu codigo de verificação de tipo que lança RuntimeException
        return true;
    }
}
  • 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 with Gené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.

  • Already his method public boolean add(Animal a) seems to be a good alternative to use polymorphism with Generic Collection && .

Browser other questions tagged

You are not signed in. Login or sign up in order to post.