Why does not polymorphism work with Generics?

15

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: can not convert from ArrayList to List

The following example also makes use of polymorphism though 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
    }
}

In this example we use the polymorphism with Arrays, which would be somewhat similar to List, and run without problems.

Why can not I use Generic polymorphism?

    
asked by anonymous 10.03.2014 / 00:06

2 answers

17

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.

    
10.03.2014 / 00:06
3

There is a misinterpretation in your question when you say "Why does not polymorphism work with Generics?" because the code List dogs = new ArrayList (); is polymorphic in relation to the List / ArraList collections. In fact it could be any Type in the java.util.Collection inheritance string that would compile.

The problem I see is another:

The line below compiles

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

But it is not secure since

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

also compiles but is semantically wrong. A runtime error of type ClassCastException will occur

The approach with generic types is more secure, but the programmer is responsible for creating a mechanism that ensures that there is no type mismatch, which in this example is guaranteed by the add method of the ListaCachorro class. In real life we will use an Interface but 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;
    }
}
    
10.03.2014 / 02:59