Programação genérica consiste na criação de estruturas de programação que podem ser usadas com tipos de dados diferentes
Exemplo: a classe ArrayList<???> é genérica
Estrutura de dados que pode ser instanciada para coleções de diferentes tipos de dados
O tipo é verificado durante a compilação do programa
Uma das principais modificações do Java 1.5
Permitem criar elementos com tipos parametrizáveis
Evitam a escrita de código repetitivo e sujeito a erros de execução resultante do uso excessivo de conversores de tipo (casts)
Em Java pode-se fazer programação genérica usando-se herança (será visto mais adiante) ou variáveis de tipo
Uma classe genérica terá uma ou mais variáveis de tipo
O uso de variáveis de tipo torna o código mais seguro e simples de ler
Nome da variável de tipo | Significado |
---|---|
E | Elemento de uma coleção. |
K | Chave de um mapa |
V | Valor em um mapa |
T | Tipo genérico |
S, U | Tipos adicionais |
Parâmetros de tipo são declarados entre < e > ao lado do nome da classe
Uma vez declarado, um parâmetro de tipo pode ser usado no lugar de qualquer tipo de dado (declaração de variáveis e atributos, parâmetros e valores de retorno)
Como instanciar?
NomeClasseGenerica<Tipo1, Tipo2, ... > n = new NomeClasseGenerica<>();
Java 7: não é necessário repetir os parâmetros de tipo do lado direito da atribuição
Observação: a ausência do parâmetro de tipo em uma classe genérica implica na utilização do tipo Object como default (será discutido mais adiante)
public class Dado {
private String dado;
public Dado(String d) { dado = d; }
public String getDado() { return dado; }
}
...
// Uso correto
Dado d = new Dado(“Oi”);
String x = d.getDado();
...
// Uso incorreto: tipo deve ser String
Dado d = new Dado(new Pessoa(“Ze”,20));
Pessoa x = (Pessoa)d.getDado();
public class Dado<E> {
private E dado;
public Dado(E d) { dado = d; }
public E getDado() { return dado; }
}
...
Dado<String> d = new Dado<>(“Ola”);
String x = d.getDado();
...
Dado<Pessoa> d = new Dado<>(new Pessoa(“Ze”,20));
Pessoa x = d.getDado();
...
Java não cria um tipo específico para cada instância de uma estrutura genérica
Durante a compilação as anotações entre "<" e ">" são apagadas, e ocorre uma tradução para código Java tradicional com os tipos e casts adequados
<li>Imagine como poderíamos reutilizar esta classe se, dependendo da empresa onde o sistema for
implantado, o código do produto pode ser:
<li>Solução: usar parâmetros de tipo!</li>
public class Produto
{
private int codigo;
private String descricao;
private double preco;
public Produto(int cod,
String descr,double pr) {
codigo = cod;
descricao = descr;
preco = pr;
}
public int getCodigo() { return codigo; }
public String getDescricao() {
return descricao;
}
public double getPreco() { return preco; }
@Override
public String toString() {
return "Produto{" + "codigo=" +
codigo + ", descricao=" + descricao +
", preco=" + preco + '}';
}
}
public class ProdutoG<T> {
private T codigo;
private String descricao;
private double preco;
public ProdutoG(T cod,String descr, double pr) {
codigo = cod;
descricao = descr;
preco = pr;
}
public T getCodigo() { return codigo; }
public String getDescricao() { return descricao; }
public double getPreco() { return preco; }
@Override
public String toString() {
return "ProdutoG{" + "codigo=" + codigo + ", descricao=" +
descricao + ", preco=" + preco + "}";
}
}
ProdutoG<String> p1 = new ProdutoG<String>("AA112","Radio",438);
ProdutoG<Integer> p2 = new ProdutoG<Integer>(112,"Radio",438);
ProdutoG<Double> p3 = new ProdutoG<Double>(112.3,"Radio",438);
public class ProdutoG2<T,U> {
private T codigo;
private String descricao;
private U preco;
public ProdutoG2(T cod, String descr, U pr) {
codigo = cod;
descricao = descr;
preco = pr;
}
public T getCodigo() { return codigo; }
public String getDescricao() { return descricao; }
public U getPreco() { return preco; }
@Override
public String toString() {
return "ProdutoG2 {" + "codigo=" + codigo + ", descricao=" +
descricao + ", preco=" + preco + "}";
}
}
ProdutoG2P<Integer,Float> p4 = new ProdutoG2P<>(112,"TV",1276F);
ProdutoG2P<String,Integer> p5 = new ProdutoG2P<>(“AB”,"TV",1276);
É possível criar genéricos limitados a uma certa "família" de classes
Exemplo:
public class MinhaLista<E extends Produto> { ... }
O exemplo define uma classe MinhaLista que pode conter quaisquer elementos cujo tipo seja subclasse ou implementação de Produto
Este tipo de restrição é chamado de "limite superior" (upper bounds)
Não importando se Produto é uma classe ou interface usa-se a palavra reservada extends
Futuramente serão vistas aplicações
É possível criar também restrições de limite inferior (lower bounds) que são de utilização mais rara
Exemplo:
public class MinhaLista<E>
{
...
public void metodo(List<? super Enlatado> li) { ... }
}
O exemplo apresenta uma lista cujos elementos devem ser Enlatado ou superclasses de Enlatado
Por exemplo, se Enlatado é derivado de Produto, então Produto é um tipo de elemento aceito na coleção
Implemente uma classe Par que possa armazenar dois objetos declarados como tipos genéricos
Demonstrar o uso da classe Par em uma classe App que possua o método main e permita criar e imprimir objetos par que contém cinco tipos diferentes de pares, como, por exemplo:
<String, Double>
(nome e nota de um aluno)<Integer,String>
(código e nome de um funcionário)<Float,Float>
(coordenadas x e y)Imagine que uma empresa codifica seus produtos em 2 partes: as letras indicam o setor onde o produto é fabricado e os números são um código sequencial dentro do setor
Ex: “IMP34”, “SEC1413”, ...
Crie uma classe chamada Codigo para representar esses códigos e instancie a classe ProdutoG vista anteriormente usando a classe Codigo como tipo do código do produto
Imagine que os tipos dos componentes da classe Codigo podem variar: altere a classe Codigo para que use 2 parâmetros de tipo (para a primeira e a segunda partes do código) e use esta nova classe para instanciar a classe ProdutoG
Um dicionário é uma estrutura onde os elementos são armazenados sob uma chave. Por exemplo, podemos armazenar os dados sobre um automóvel usando como chave a placa. Para recuperar os dados do automóvel basta informar a placa (ao invés, por exemplo, da posição onde os dados foram armazenados)
Usando dois arrays, crie uma classe Dicionario onde tanto a chave como o valor tem seu tipo definido por parâmetros de tipo
Imagine que as seguintes operações devem estar disponíveis:
add(K chave, V valor)
- adiciona à chave K o valor VV get(K chave)
- retorna o valor associado à chave K (se não encontrar, retorna null)Obs: para criar um array de genéricos, use:
E[] vet = (E[]) new Object[TAMANHO];
Horstmann, capítulo 17 (Genéricos)
Goodrich, seção 2.5.2 (Genéricos)