• Skip to primary navigation
  • Skip to content

Mauda

IT, Java and Music

Graduação   SCJP   Mestrado
  • Apresentação
  • Certificação Java
  • JPA
    • Exceptions
  • JSF
  • Versionamento
  • Contato

Java Generics – Básico do Básico

March 2, 2015 by Mauda 2 Comments

Conteúdo do Post:
  1. O que é Generics?
  2. Como Generics funcionam?
  3. Exemplos Simples
  4. Notações e Convenção de Nomes
  5. Benefícios e Vantagens
  6. Type-Safety
  7. Abaixo aos Casts!
  8. Extinção do ClassCastException!
  9. Pontos Importantes
  10. finally{

Olá Pessoal, tudo bom?

No post de hoje iremos falar sobre um tema que muitos desenvolvedores utilizam, mas poucos compreendem. Vamos falar sobre Generics ou aqueles sinais malucos de maior e menor que são utilizados ao declarar uma instância de List 😀

Essa é mais uma importante adição realizada pela versão 1.5 do Java e que é muito utilizada, mas é um dos temas mais complicados de compreensão que existem na linguagem Java, dessa forma esse post visa detalhar um pouco melhor Generics mas de uma forma um pouco mais amigável principalmente para iniciantes no desenvolvimento Java.

Grande parte deste post foi elaborado utilizado o livro Java Generics and Collections ((Java Generics and Collections – http://shop.oreilly.com/product/9780596527754.do)), assim fica a dica para quem quiser se aprofundar mais no tema.

O que é Generics?

Generic é um nova extensão adicionada na linguagem Java a partir da versão 1.5 a qual provê em tempo de compilação uma verificação de type-safety de código, removendo riscos de ClassCastException durante a execução, o qual era um erro comum antes da versão 1.5; Essa verificação consiste em verificar se o que está sendo atribuído a uma instância de uma classe está de acordo com o especificado. Por exemplo, veja o código da Listagem 1:

Listagem 1 - Código Antes e Depois de Generics
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///////////////////////////////////////////////////
// Antes do Java 1.5
///////////////////////////////////////////////////
 
List palavras = new ArrayList();
palavras.add("ABC"); //Tudo ok, compatível com o especificado
palavras.add(12);    //Erro de ClassCastException em tempo de execução
 
///////////////////////////////////////////////////
// Após o Java 1.5
///////////////////////////////////////////////////
 
List<String> palavras = new ArrayList<String>();
palavras.add("ABC"); //Tudo ok, compatível com o especificado
palavras.add(12); //Erro de compilação

Repare que os erros que ocorrem na linha de adição do integer 12. Antes do Java 1.5, o erro ocorria somente em execução, o que dependendo do caso poderia ser pego somente quando o software estivesse em produção. Já após o Java 1.5, a linha que adiciona o integer 12 daria um erro de compilação, devendo ser corrigido tem tempo de desenvolvimento do software.

Ao utilizar Generics o javac age como um corretor ortográfico. Ao escrever um código no qual existem erros na tipagem, o javac irá mostrar esses erros e pedir para você realizar a correção. Dependendo do erro, ele pode mostrar uma possível correção, mas nem sempre isso pode ocorrer.

Generics chegou atrasado para ser utilizado com o framwork da Collections, que permitiu uma abstração excelente sobre estruturas de dados. Apesar de parecer muito complexa devido aos sinais de menor-maior ( < , > ) e pelos seus “wildcards”, a partir do momento que você compreende o porque da utilização e como realizar esta utilização, você deverá se sentir muito confortável para utilizar Generics em seus códigos.

Se você for um iniciante que não está tão familiarizado com Generics é uma excelente hora de preencher essa lacuna existe em sua carreira 🙂 . Não apenas irá ajudar a evitar umas ClassCastException em execução como irá dar mais confiança sobre o comportamento de seu código.

Um último detalhe é que Generics podem ser aplicadas em classes, métodos e atributos, além de interfaces e enums. Assim você pode utilizar as notações para atender necessidades que forem sendo criadas em seus projetos.

Como Generics funcionam?

Vamos relembrar como funciona como é o processo de compilação de uma classe Java de acordo com a Figura 01

Figura 01 – Como funciona a compilação de um código Java

Após verificar em tempo de compilação se a sintaxe do código está de acordo com as específicações de Generics e Java, o compilador irá gerar o código em Java byte-codes, mas sem a sintaxe generics, convertendo isso para o que é chamado de raw type. Todo o tipo de informação relacionada ao generics é removida durante essa fase, o nome desta operação é chamada Type Erasure.

Assim lembrando do código da Listagem 1 acima, o código voltaria ao que era na versão antes da 1.5. Esse processo é realizado por detrás dos panos, não sendo necessária a preocupação se está sendo realizado da maneira correta, pois em tempo de compilação o javac já garantiu que o código é type-safe. Dessa forma é fácil perceber que Generics nada mais é que um mecanismo para verificar se o que você está desenvolvendo está nos trilhos corretos. Se tudo estiver ok, não existirá erros de compilação. Senão existem erros é possível executar esse código. Se é possível executar é possível acreditar que não ocorrerão erros de type cast.

Exemplos Simples

Vamos começar retratando alguns exemplos simples de Generics

1) Existem muitas interfaces e classes que são parametrizadas, como por exemplo a classe java.util.Set ((API java.util.Set – http://docs.oracle.com/javase/7/docs/api/java/util/Set.html)), dessa forma é possível criar classes “dinâmicas” de tipos como na Listagem 2:

Listagem 2 - Classe com tipagem
Java
1
2
Set sets = new HashSet<String>();
sets = new HashSet<Integer>();

Lógico que a instância do HashSet não é dinâmica, apenas o ponteiro desta pode apontar para Hashs de vários tipos, por não limitarmos o ponteiro para um determinado tipo.

 

2) Ao utilizar um generic com o tipo como Object, isso deixará a classe parametrizada para qualquer tipo de objeto, ou seja, podemos inserir Strings, Integers, Aviões, Ambientes e o que mais for necessário neste tipo de classe. Mas independente do tipo, um set de uma classe dessas será sempre do tipo Object, podendo ocorrer ClassCastExceptions, por causa dos casts necessários após o set.

Listagem 3 - Exemplo do uso do generic Object
Java
1
2
3
4
5
6
List<Object> listQualquerTipo = new ArrayList<Object>();
listQualquerTipo.add("abc");             //Sem problemas de compilação
listQualquerTipo.add(new Float(3.0f));   //Sem problemas de compilação
for(int i = 0; i < list.size(); i++){
    String s = (String) list.get(i);     //Ocorrerá erro de execução na segunda iteração
}

 

3) Um classe que é parametrizada com o ? (Interrogação) representa uma classe de parâmetros de um tipo desconhecido. Dessa forma você pode atribuir a ela qualquer tipo, como String ou Integer.

Listagem 4 - Uso do generic ?
Java
1
2
List<?> listaTipoDesconhecido = new ArrayList<String>();
listaTipoDesconhecido = new ArrayList<Integer>();

 

4) Ao trabalhar com classes parametrizadas que possuem um hierarquia de herança, é possível instanciar classes filhas para que um ponteiro da classe pai receba essas instâncias.

Java
1
2
Set<String> setString = new HashSet<String>(); //Valido
setString = new LinkedHashSet<String>();       //Valido

Mas é uma herança entre as classes, pois o parametro do generic não suporta diretamente

Java
1
Set<Object> SetOfObject = new HashSet<String>(); //Erro de compilação - Tipos incompatíveis

 

5) Mas existem formas do parâmetro do Generics aceitar herança, basta utilizar a palavra chave extends e a classe pai requerida, por exemplo. Isso é chamado Bounded Type Parameters:

Java
1
2
3
Set<? extends Number> setDeNumbers = new HashSet<Integer>(); //OK - Integer extends Number
setDeNumbers = new HashSet<Float>(); //OK - Float extends Number
setDeNumbers = new HashSet<String>();//Erro - String não extends Number

 

6) Da mesma forma é possível utilizar a palavra chave super, para utilizar uma herança ao inverso, ou ainda as interfaces que esta implementa e ainda observando a classe em si, por exemplo:

Java
1
2
3
Set<? super TreeMap> setOfAllSuperTypeOfTreeMap = new LinkedHashSet<TreeMap>(); //OK, TreeMap é super dela mesma
setOfAllSuperTypeOfTreeMap = new HashSet<AbstractMap>(); //OK AbstractMap é pai da classe TreeMap
setOfAllSuperTypeOfTreeMap = new LinkedHashSet<Map>();   //OK Map é uma interface implementada por TreeMap

 

7) Não é possível utilizar Generics nas classes literais (.class), sendo o único local que não é permitido utilizar o generics e sim somente o raw type.

Java
1
2
List.class          //OK
List<String>.class  //Erro

 

8) Ao desenvolver métodos com Generics, é necessário declarar os tipos dos parâmetros na assinatura do método, entre os modificadores e o tipo do retorno, por exemplo:

Java
1
2
3
public static <T> T igual(T origem){
    return origem;
}

Esse parâmetro T deverá ser criado na declaração da classe, pois senão ocorrerá erro de compilação.

 

Notações e Convenção de Nomes

Uma das razões de Generics não parecer ser simples, está relacionado a quantidade de termos e convenções para nomes que existem. A partir do momento em que estes itens se tornam mais familiar, com o conhecimento do para que serve cada um, torna-se mais fácil a interpretação destes símbolos. A seguir as notações mais comuns ao utilizar Generics:

Notação Significado
<E>  Tipo Genérico; É chamado de um parâmetro formal; Utilizado para denotar um Elemento
<T> Tipo Genérico; É chamado de um parâmetro formal; Utilizado para denotar um Tipo
<Integer> Tipo Parametrizado como uma classe Integer
<?> Representa uma classe desconhecida
 Set Raw Type, ou seja, uma classe desprovida de notações generics
 <? extends T> Representa o tipo parametrizado como uma classe que seja da instância de T ou filha de T
<T extends Comparable> Representa que o tipo parametrizado T seja uma classe que implemente a interface Comparable
<T extends Aviao> Representa que o tipo parametrizado T seja uma classe que implemente a classe Aviao
<? Super T> Representa o tipo parametrizado T que seja uma instância de T ou pai de T, seja classe ou interface
 <T super Comparable> Representa que o tipo parametrizado T implemente a interface Comparable ou um de seus pais
 <T extends Comparable<T>> Tipo Parametrizado Recursivo, T deve ser do tipo de uma classe que realize uma implementação da interface Comparable para comparações com a classe T
public abstract class BilheteBC<E extends Bilhete> extends PatternBC<E> Declaração de uma classe que recebe um tipo parametrizado E, o qual estende a classe Bilhete. Além disso a classe BilheteBC estende a classe PatternBC a qual é obrigada a receber instâncias de E

 

Benefícios e Vantagens

A introdução de Generics dentro da Linguagem Java trouxe muitos benefícios para o desenvolvimento, como por exemplo:

Type-Safety

Sem dúvida é a principal vantagem do Generics. O framework Collections, antes da JDK 1.5 era desprotegido dessa segurança, pois acabava tendo que aceitar qualquer tipo de Object, independente se a collection poderia conter apenas Strings, por exemplo. A partir da introdução do Generics, criou-se o conceito de type-safe collection, pois ao criar uma collection de Strings essa automaticamente estará protegida contra outros tipos e classes, evitando assim muitas ClassCastExceptions já em tempo de compilação.

Java
1
2
3
4
5
List<String> palavras = new ArrayList<String>();
 
palavras.add(“coins”);      //OK, sem erros
palavras.add(new Aviao());  //Erro! A list não aceita avioes, somente Strings
palavras.add(42);           //Erro! A list não aceita o sentido da vida e do universo!

 

Abaixo aos Casts!

Com Generics não é necessário realizar type casts, pois ele o faz automaticamente para você! Veja abaixo como era na versão pré 1.5 e agora a partir da versão 1.5, repare que deixa o código mais claro e robusto!

Listagem 1 - Código Antes e Depois de Generics
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///////////////////////////////////////////////////
// Antes do Java 1.5
///////////////////////////////////////////////////
 
List  itens = new ArrayList();
itens.add("chocolates");
String item = (String) itens.get(0)
 
///////////////////////////////////////////////////
// Após o Java 1.5
///////////////////////////////////////////////////
 
List<String> itens = new ArrayList();
itens.add("chocolates");
String item = itens.get(0) //Cade o cast que estava aqui??? Tá no Generics!

 

Extinção do ClassCastException!

Com o compilador javac garantindo que as classes receberão os tipos corretos no momento da compilação do código Java, a exception ClassCastException praticamente desapareceu dos programas Java! Isso é excelente!

 

Pontos Importantes

Generics não podem ser aplicadas sobre tipos primitivos (int, long, char, boolean). Mesmo com a existência das classes Wrappers (Integer, Long, Character, Boolean) e o auto-boxing (conversão automática entre tipo primitivo e wrapper – int e Integer, por exemplo) não é possível utilizar tipos primitivos com Generics. Meio estranho mas é a vida!

Java
1
Holder<int> numbers = new Holder<int>(10); //Erro de compilação! unexpected type required: reference found:int

 

Uma classe, interface parametrizada com Generics usa tipos formais para recuperar a informação do tipo ao criar uma nova instância dessa classe.  Por exemplo o código da seguinte interface:

Java
1
2
3
4
public interface Cache <K,V>{
    public V get();
    public V put(K key, V value);
}

Ao criar uma classe que implemente uma classe parametrizada, essa interface por exemplo, é necessário passar as classes que serão representadas por esses valores, por exemplo Integer e String:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TesteCache implements Cache<Integer, String>{
 
@Override
public String get() {
//Escreva algo aqui
return null;
}
 
@Override
public String put(Integer key, String value) {
//Escreva algo aqui
return null;
}
}

 

Generics são relacionadas com Templates em C++, mas diferente deles, que criam novos tipos para cada parâmetro específico, em java as classes parametrizadas são compiladas apenas uma vez e mais importante é que é gerado apenas um único arquivo, visto que Generics é utilizado na fase de compilação e na execução o javac remove todas as notações referentes a Generics.

 

Type Inference: A partir da versão 7 o Java foi acrescido de um novo operador, o diamont (<>). A utilidade deste operador está na redução do código redundante durante a instanciação de uma classe parametrizada. Assim o Generic declarado na variável que irá armazenar a classe é entendido pelo javac como o mesmo na chamada ao construtor.

 

Java
1
2
3
4
5
6
7
8
///////////////////////////////////////////////////
// Antes do Java 7
///////////////////////////////////////////////////
Map<String, Set<Integer>> contacts = new HashMap<String, Set<Integer>>();
///////////////////////////////////////////////////
// Após o Java 7
///////////////////////////////////////////////////
Map<String, Set<Integer>> contacts = new HashMap<>();

 

 

finally{

Generics é um assunto extremamente vasto em Java. O Objetivo deste post foi explicar os itens mais básicos, iniciando a pavimentação de um longo que caminho que exploraremos ao longo dos futuros posts deste site.

Duvidas ou sugestões? Deixe seu feedback! Isso ajuda a saber a sua opinião sobre os artigos e melhorá-los para o futuro! Isso é muito importante!

Até um próximo post!

Filed Under: Java, Tutorial Tagged With: Generics, Java

About Mauda

Mestre em Informática, Analista de Sistemas, Professor, SCJP e Baterista. Desde 2002 trabalhando no mundo Java e ensinando pessoas sobre desenvolvimento de sistemas. Mais informações

Reader Interactions

Comments

  1. Douglas Oliveira says

    May 26, 2015 at 1:33 pm

    Muito bom, Parabens,

    Mas algumas coisas me deixaram com duvidas em relação a interrogação (?), se ela serve para se referenciar a qualquer tipo, o T não supriria essa necessidade ?

    Exemplo
    List lista; seria a mesma coisa que List ?

    Reply
    • Mauda says

      May 28, 2015 at 2:59 pm

      Olá Douglas, tudo bom?

      Obrigado! e Desculpe a demora em responder.

      O ponto de interrogação (?) realiza o mesmo trabalho do T, mas diferente do T você não pode setá-lo em uma declaração de atributo, por exemplo:

      public void compare(List< ?> lista){
      for(? elemento : lista){ //ERRO DE COMPILACAO
      //realiza uma operacao de comparacao
      }
      }

      Se tiver mais dúvidas por favor faça um novo comentário!

      Obrigado.

      Mauda

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Advertisements

Copyright © 2025 · Genesis Framework · WordPress · Log in