Dúvida em Generics - "COMO É QUE ELE SABE?"

19 respostas
gomesrod

Olá pessoal,

Vejam o seguinte código, um rápido exemplo utilizando generics:

class Animal {}
class Gato extends Animal {}

public class GenericTest {

	public static void main(String[] args) {
		List<Gato> l = new ArrayList<Gato>();
		workWithList(l);
	}

	private static void workWithList(List<? extends Animal> lst) {
		lst.get(0);          // OK
		lst.add(new Gato()); // NAO COMPILA !
	}
}

Até aí tudo bem, já entendi porque a segunda linha não compila, quais os inconvenientes que poderiam ocorrer.
Minha dúvida é a seguinte: como o compilador sabe que a primeira linha é “segura” e a segunda não? Afinal na declaração dos métodos (interface List) não há nada que indique isso:

// Interface List E get(int index); boolean add(E o);

Essa questão está me intrigando… E aí, é feitiçaria ou tecnologia? :smiley:

19 Respostas

T
E get(int index);  
boolean add(E o);

Dica: a posição do E é diferente nos dois casos (na primeira rotina, E é um tipo de retorno. No segundo método, E é um tipo de um parâmetro).

Regras diferentes se aplicam quando você tem um tipo no lado esquerdo (L-Value) ou direito (R-Value) de uma atribuição.

renatocustodio

Bom, sabendo que não vai poder adicionar algo que é inválido, então na teoria tudo que pegar de volta vai ser válido.

B

<< Novo em Generics

lst.add(new Gato()); // NAO COMPILA !Por que não?

marcosbrandao

Não compila porque você passou no parametro o caracter “?”.

Nesta situação, você não pode adicionar nada, somente consultar.
Isso acontece porque deste jeito você proteje objeto que está passando por parametro não deixando adicionar qualquer coisa dentro da lista.

Por Exemplo, você poderia estar passando um Lista que com objetos Gato. Mas dentro do metodo você poderia adicionar um Dog, e Dog não é um Gato, mas é um animal.

lucamartins
Tenta assim.
class Animal {}   
class Gato extends Animal {}   
  
class GenericTest {   
  
    public static void main(String[] args) {   
        List&lt;Animal&gt; l = new ArrayList&lt;Animal&gt;();   
        workWithList(l);   
    }   
  
    private static void workWithList(List&lt;Animal&gt; lst) {   
        lst.get(0);          // OK   
        lst.add(new Gato()); // AGORA COMPILA TB!   
    }   
}
O polimorfismo fica implicito!
ViniGodoy

É luca, mas nesse caso você já não pode mais passar um List<Cachorro>, List<Gato>, List<Cavalo> ou qualquer outro list que seja filho de animal para o método.

E, quando se usa esse tipo o Wildcard ? você quer ter esse tipo de liberdade.

lucamartins

ViniGodoy:
É luca, mas nesse caso você já não pode mais passar um List<Cachorro>, List<Gato>, List<Cavalo> ou qualquer outro list que seja filho de animal para o método.

E, quando se usa esse tipo o Wildcard ? você quer ter esse tipo de liberdade.

Claro, apenas sugeri um codigo compilavel. Na minha opnião, a melhor implementação seria:

class Animal {}   
class Gato extends Animal {}   
class Cachorro extends Animal {}
class Pato extends Animal {}
  
class GenericTest {   
  
    public static void main(String[] args) {   
        List&lt;Animal&gt; l = new ArrayList&lt;Animal&gt;();   
        workWithList(l, new Gato());   
        workWithList(l, new Cachorro());   
        workWithList(l, new Pato());   
    }   
  
    private static &lt;T extends Animal&gt; void workWithList(List&lt;T&gt; lst, T t) {   
        lst.get(0); // OK   
        lst.add(t); // NAO COMPILA !   
    }   
}
Javabuntu

Quando você usa o “?” no argumento de um método, seja ? extends, ou ? super, você NÃO PODE, JAMAIS, NUNCA, NEVER, adicionar.

Você estará dizendo o seguinte: "vou receber uma Collection e PROMETO não usar add, não vou adicionar nada, prometo do fundo do meu coração, e os generics verificam isso em tempo de COMPILAÇÃO.

por isso você não pode usar add, se tirar o “?” ai pode usar tranquilo…

ViniGodoy

Eu sei que você sabia. Só falei mais para deixar claro, pq tem muita gente nova em Generics no tópico. :wink:

Outra solução seria fazer uma cópia da lista e retornar a cópia.

class Animal {}   
class Gato extends Animal {}   
class Cachorro extends Animal {}
class Pato extends Animal {}
  
class GenericTest {   
  
    public static void main(String[] args) {   
        List&lt;Animal&gt; l = new ArrayList&lt;Animal&gt;();   
        l = workWithList(l, new Gato());   
        l = workWithList(l, new Cachorro());   
        l = workWithList(l, new Pato());   
    }   
  
    private static &lt;T extends Animal&gt; List&lt;Animal&gt; workWithList(List&lt;T&gt; lst, T t) {   
        List&lt;Animal&gt; animais = new ArrayList&lt;Animal&gt;(lst);
        animais.get(0); // OK   
        animais.add(t); // OK, é outra lista mesmo !   
        return animais;
    }   
}

Nesse exemplo ficou meio bobo. Mas é sempre bom lembrar que esse tipo de cópia é possível. Pode salvar sua pele.

marcosbrandao

Só corrigindo um ponto:

quando usa " ? extends AlgumaCoisa" está correto a sua afirmação. Mas quando você usa “? super AlgumaCoisa”, você pode sim adicionar. Por que o argumento poderá ser qualquer coisa do mesmo tipo ou de um tipo superior que o Objeto que está sendo adicionado, ou seja, o Objeto adicionado sempre será “compatível” com o argumento.

Por exemplo, um argumento “? super Dog” pode ser adicionado qualquer Dog, Mamifero, Animal, SerVivo. Por que Dog é um Mamifero, Mamifero é um Animal e Animal é um SerVivo.

bl.rafael

private static <T extends Animal> List<Animal> workWithList(List<T> lst, T t) { List<Animal> animais = new ArrayList<Animal>(lst); animais.get(0); // OK animais.add(t); // OK, é outra lista mesmo ! return animais; }

Aproveitando o tópico, estou estudando para a SCJP 5 e ainda tenho dúvida quanto a Generics, já que não utilizo no projeto que trabalho java 5.

Na assinatura do método acima existe:

… List …

Para que colocar o antes do tipo de retorno? O tipo de retorno não é definido por List?
Alguém poderia dar uma aulinha de generics? =)

bl.rafael

Nossa acho que entendi, não existe diferença de declarar o tipo do T antes ou depois …
A diferença do primeiro código do método do post para o que abordei é que está definindo o tipo do generico, correto?

T

O “” não modifica o tipo de retorno (que no caso é List); ele está modificando o método inteiro (é que como você deve ter visto, nos parâmetros aparece T.)

Se você quer a aula e sabe um pouco de Inglês, pode tentar ler o tutorial de generics no Java Tutorial, http://java.sun.com/docs/books/tutorial/extra/generics/index.html ou http://java.sun.com/docs/books/tutorial/java/generics/index.html

T

Em C# (que copiou um monte de coisas do Java) existe uma construção semelhante, mas que acho um pouco mais clara.

Para ajudar na tradução, java.util.List -> System.Collections.Generic.IList, java.util.ArrayList -> System.Collections.Generic.List, <T extends Animal> -> where T : Animal. Veja que nesse caso o "where", como você deve ter percebido, realmente modifica o método inteiro, não o tipo de retorno.

private static IList&lt;Animal&gt; workWithList(IList&lt;T&gt; lst, T t) 
        where T: Animal {     
        IList&lt;Animal&gt; animais = new List&lt;Animal&gt;(lst);   
        Animal firstAnimal = animais[0]; 
        animais.Add(t); 
        return animais;   
    }
bl.rafael

O que eu quis dizer é o seguinte, no primeiro método abordado, a assinatura era:

private static List<Animal> workWithList(List<? extends Animal> lst) { lst.get(0); // OK lst.add(new Gato()); // NAO COMPILA ! return lst; }

e no segundo essa assinatura:

private static <T extends Animal> List<Animal> workWithList(List<T> lst) { lst.get(0); // OK lst.add(new Gato()); // NAO COMPILA ! return lst; }

A minha dúvida é, a diferença dos métodos acima é que um declara o tipo T antes, e no outro já é passado o tipo do generics por parâmetro?

L

Bom, para mim ficou ligeiramente confuso..
o código abaixo compila.

Veja bem Ele cria uma lista de Animais, ou seja supertype de Felino
Porém internamente a classe , eu crio um Gato, ou seja filho de felino..........

A impressão que dá é que, na criação de objetos, vale criar Filhos , na declaração dos tipos vale o super Generic.

import java.util.ArrayList;
import java.util.List;

 class Animal {}  
 class Felino extends Animal {}  
 class Gato extends Felino{}  
 
 public class GenericTest {  
   
     public static void main(String[] args) {  
         List<Animal> l = new ArrayList<Animal>();  
         workWithList(l);
     }  
   
     private static void workWithList(List<? super Felino> g) {  
         //lst.get(0);          // OK  
         g.add(new Gato()); // NAO COMPILA !  
     }  
 }
L

private static <T extends Animal> List<Animal> workWithList(List<T> lst) { lst.get(0); // OK lst.add(new Gato()); // NAO COMPILA ! return lst; }
Esse não compila…

private static <T extends Animal> List<T> workWithList(List<T> lst) { lst.get(0); // OK //lst.add(new Gato()); // NAO COMPILA ! return lst; }

Os dois não compilam… A questão é que o T extends Animal, mas vc nao sabe ainda o tipo que é, pois vai depender do tipo que foi inserido pela lista… na verdade o compilador vai inferir. Então é natural que não compile o primeiro caso.

E a diferença entre os dois é que no caso da " ? " , você pode ter qualquer coisa que extenda Animal, porém no outro tem que ser um tipo definido.Ou seja o tipo de retorno é o mesmo que o do paramâtro de entrada… Ainda assim, como vocÊ exige um retorno do tipo Animal, então não tem jeito… Dá Erro também.

Agora não sei como converter o codigo abaixo para algo que rode e que não fique parecido com o exemplo anterior…

private static List<Animal> workWithList(List<? extends Animal> lst) { lst.get(0); // OK lst.add(new Gato()); // NAO COMPILA ! return lst; }

Paulo_Silveira

Creio que sua linha 3 nao compile, pois List<? extends Animal> não é uma List

marcosbrandao

Paulo Silveira:
leonardocregis:

private static List<Animal> workWithList(List<? extends Animal> lst) { lst.get(0); // OK lst.add(new Gato()); // NAO COMPILA ! return lst; }

Creio que sua linha 3 nao compile, pois List<? extends Animal> não é uma List


Com certeza não compila!
O compilador do java não deixa para assegurar que nenhum tipo diferente de Animal seja adicionado na lista.
Neste caso eu poderia estar passando no Argumento uma Lista de Dog, e no método adicionando um Cat na lista de Dog. Isso poderia causar um ClassCastException lá na frente. Por isso o compilador encontra um erro aí.

Criado 30 de abril de 2008
Ultima resposta 6 de mai. de 2008
Respostas 19
Participantes 11