JAVA Learning

Hub de Conteúdo

Generics em Java

Type Safety, Reutilização de Código e Eliminação de Casting

1. O que são Generics?

Generics permitem criar classes, interfaces e métodos que funcionam com diferentes tipos enquanto mantêm type safety (segurança de tipos).

Ao invés de trabalhar com Object, você especifica o tipo quando usa a classe.

Generics eliminam a necessidade de casting e permitem erros de tipo serem detectados em tempo de compilação.

2. O Problema sem Generics

Sem Generics, era necessário fazer casting manual e havia risco de erros em runtime.

❌ Sem Generics (Java antigo):

java
// Sem generics - trabalha com Object
ArrayList lista = new ArrayList();
lista.add("Java");
lista.add(123); // Diferente tipo!

// Precisa fazer casting
String s = (String) lista.get(0); // OK
String numero = (String) lista.get(1); // ClassCastException!

// Problema: erro só aparece em runtime

✅ Com Generics (Moderno):

java
// Com generics - type safety
ArrayList lista = new ArrayList<>();
lista.add("Java");
lista.add(123); // ERRO em COMPILE TIME!

// Sem casting
String s = lista.get(0); // Automático

// Problema prevenido em tempo de compilação

3. Sintaxe Básica de Generics

Generics usam type parameters entre <>.

java
// Genérico com um tipo
ArrayList nomes = new ArrayList<>();
ArrayList numeros = new ArrayList<>();
ArrayList pessoas = new ArrayList<>();

// Genérico com múltiplos tipos
Map notas = new HashMap<>();

// Iteração é type-safe
for (String nome : nomes) {
    System.out.println(nome); // Já sabe que é String
}

// Sem casting necessário
Integer primeiro = numeros.get(0); // Automático

4. Criando Classes Genéricas

java
// Classe genérica simples
public class Caixa {
    private T conteudo;
    
    public void colocar(T item) {
        this.conteudo = item;
    }
    
    public T remover() {
        return conteudo;
    }
    
    public T obter() {
        return conteudo;
    }
}

// USO
Caixa caixaTexto = new Caixa<>();
caixaTexto.colocar("Java");
System.out.println(caixaTexto.obter()); // Java

Caixa caixaNumero = new Caixa<>();
caixaNumero.colocar(42);
System.out.println(caixaNumero.obter()); // 42

// Múltiplos type parameters
public class Par {
    private K chave;
    private V valor;
    
    public Par(K chave, V valor) {
        this.chave = chave;
        this.valor = valor;
    }
    
    public K getChave() { return chave; }
    public V getValor() { return valor; }
}

// USO
Par par = new Par<>("idade", 25);
System.out.println(par.getChave() + ": " + par.getValor());

5. Métodos Genéricos

java
// Método genérico
public class Utilitarios {
    
    // Tipo T é local ao método
    public static  void imprimir(T elemento) {
        System.out.println(elemento);
    }
    
    // Múltiplos tipos
    public static  void exibir(K chave, V valor) {
        System.out.println(chave + " = " + valor);
    }
    
    // Com tipo de retorno
    public static  T primeiro(T[] array) {
        return array.length > 0 ? array[0] : null;
    }
    
    // Encontrar máximo
    public static > T maximo(T[] array) {
        if (array.length == 0) return null;
        T max = array[0];
        for (T elemento : array) {
            if (elemento.compareTo(max) > 0) {
                max = elemento;
            }
        }
        return max;
    }
}

// USO
Utilitarios.imprimir("Java");
Utilitarios.exibir(1, "um");

Integer[] numeros = {1, 5, 3, 9, 2};
System.out.println(Utilitarios.primeiro(numeros)); // 1
System.out.println(Utilitarios.maximo(numeros)); // 9

6. Bounded Type Parameters

Você pode limitar qual tipo pode ser usado com um generic.

java
// Extends - limite superior (upper bounded)
public class NumericoGenerico {
    private T valor;
    
    public NumericoGenerico(T valor) {
        this.valor = valor;
    }
    
    public double obterDouble() {
        return valor.doubleValue(); // Number tem este método
    }
}

// USO - funciona com tipos que estendem Number
new NumericoGenerico<>(42); // Integer extends Number - OK
new NumericoGenerico<>(3.14); // Double extends Number - OK
new NumericoGenerico<>("texto"); // ERRO - String não estende Number

// Interface como bound
public interface Comparavel {
    int comparar(Object outro);
}

public class Ordenador {
    public void ordenar(T[] array) {
        // T tem método comparar()
    }
}

// Múltiplos bounds
public > T processar(T valor) {
    return valor;
}

7. Wildcards e ? extends

Wildcards (?) permitem trabalhar com tipos desconhecidos.

java
// Wildcard - qualquer tipo
public static void imprimirLista(List lista) {
    for (Object elemento : lista) {
        System.out.println(elemento);
    }
}

// ? extends - tipo ou subtipo (covariance)
public static double somarNumeros(List lista) {
    double soma = 0;
    for (Number num : lista) {
        soma += num.doubleValue();
    }
    return soma;
}

// USO
List inteiros = Arrays.asList(1, 2, 3);
List decimais = Arrays.asList(1.5, 2.5);

System.out.println(somarNumeros(inteiros)); // 6.0
System.out.println(somarNumeros(decimais)); // 4.0

// ? super - tipo ou supertipo (contravariance)
public static void preencherComNumeros(List lista) {
    lista.add(42);
    lista.add(3.14);
    lista.add(new BigDecimal("99.99"));
}

List objetos = new ArrayList<>();
preencherComNumeros(objetos); // OK

List inteiros2 = new ArrayList<>();
preencherComNumeros(inteiros2); // ERRO - Number não é supertipo de Integer
                
            

            

8. Type Erasure

Type Erasure: Em runtime, informações de generics são apagadas. Generics existem apenas em compile-time.

java
// Compile time - tem tipo
ArrayList strings = new ArrayList<>();
ArrayList numeros = new ArrayList<>();

// Runtime - tipo é apagado
// Ambos são simplesmente ArrayList em runtime!

// Por isso ISSO não funciona:
// public void processar(ArrayList lista) { }
// public void processar(ArrayList lista) { } 
// ERRO - ambos viram ArrayList em runtime

// Nem isso:
// ArrayList lista = new ArrayList(); // ERRO compile
// Mas em runtime seria apenas ArrayList

// Implicações
public  boolean ehInstancia(Object obj, Class tipo) {
    return tipo.isInstance(obj); // Precisa de Class para runtime
}

// USO
ehInstancia("texto", String.class); // true
ehInstancia(42, Integer.class); // true

9. Exemplo Prático: Repositório Genérico

java
// Classe base
public abstract class Entidade {
    protected int id;
    public int getId() { return id; }
}

// Repositório genérico
public class Repositorio {
    private List dados = new ArrayList<>();
    private int proximoId = 1;
    
    public void salvar(T entidade) {
        entidade.id = proximoId++;
        dados.add(entidade);
        System.out.println("Salvo: " + entidade);
    }
    
    public T buscarPorId(int id) {
        return dados.stream()
            .filter(e -> e.getId() == id)
            .findFirst()
            .orElse(null);
    }
    
    public List buscarTodos() {
        return new ArrayList<>(dados);
    }
    
    public void deletar(int id) {
        dados.removeIf(e -> e.getId() == id);
    }
}

// Entidades
class Usuario extends Entidade {
    private String nome;
    public Usuario(String nome) { this.nome = nome; }
    public String toString() { return "Usuario(" + nome + ")"; }
}

class Produto extends Entidade {
    private String descricao;
    public Produto(String descricao) { this.descricao = descricao; }
    public String toString() { return "Produto(" + descricao + ")"; }
}

// USO
Repositorio repUsuario = new Repositorio<>();
repUsuario.salvar(new Usuario("João"));
repUsuario.salvar(new Usuario("Maria"));

Repositorio repProduto = new Repositorio<>();
repProduto.salvar(new Produto("Notebook"));
repProduto.salvar(new Produto("Mouse"));

System.out.println(repUsuario.buscarPorId(1)); // Usuario(João)
System.out.println(repProduto.buscarTodos()); // [Produto(Notebook), Produto(Mouse)]

10. Boas Práticas com Generics

✓ FAÇA:

  • Use generics para type safety
  • Use bounded types quando necessário
  • Prefira List a List
  • Use wildcards apropriadamente
  • Entenda type erasure

✗ EVITE:

  • Não misture tipos raw e generics
  • Não use Object desnecessariamente
  • Não assuma que generics existem em runtime
  • Não ignore warnings do compilador
  • Não use generics para primitivos (use Integer, não int)

11. Exercícios Práticos

Exercício 1: Classe Stack Genérica

Implemente uma classe Pilha<T> genérica com push(), pop(), peek().

Exercício 2: Método de Busca

Crie um método genérico que busque um elemento em um array.

Exercício 3: Bounded Types

Crie classe que funcione apenas com tipos Comparable.

Exercício 4: Repositório

Implemente repositório genérico com salvar, buscar, deletar.

Exercício 5: Cache Genérico

Crie sistema de cache que funcione com qualquer tipo.

Conclusão

Generics são essenciais para código Java seguro, reutilizável e profissional.

Use type parameters, bounded types e wildcards para criar APIs flexíveis e type-safe.

Domine generics e seu código será muito mais robusto! 🔒✨

JAVA Learning - Hub de Conteúdo SENAI

Generics: Type Safety e Reutilização!

Código seguro, flexível e profissional!