🔀Generic class

📘 O que são classes genéricas?

Classes genéricas permitem que você escreva código que pode operar sobre diferentes tipos de forma segura, reutilizável e sem casting desnecessário. Elas são muito comuns em bibliotecas Java como List<T>, Map<K,V>, Optional<T>, entre outras.

Exemplos de uso correto

🔸Exemplo 1: Classe genérica simples

public class Caixa<T> {
  private T valor;

  public void guardar(T valor) {
    this.valor = valor;
  }

  public T abrir() {
    return valor;
  }
}

Caixa<String> caixa1 = new Caixa<>();
caixa1.guardar("Olá");
System.out.println(caixa1.abrir());

Linha 1: T é o parâmetro de tipo genérico.
Linha 13: Ao instanciar com String, T será substituído por String.

🔸Exemplo 2: Classe com múltiplos tipos

public class Par<K, V> {
  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;
  }
}

Par<Integer, String> par = new Par<>(1, "um");
System.out.println(par.getChave() + " = " + par.getValor());

linha 1: K e V são tipos genéricos diferentes.
Linhas 10 e 14: Ao instanciar com Integer e String, os métodos retornam esses tipos.
Linha 19: Faz a instância com Integer e String.

🔸Exemplo 3: Restrições com bounded types

public class Somador<T extends Number> {
  private T valor;

  public Somador(T valor) {
    this.valor = valor;
  }

  public double somar(double outro) {
    return valor.doubleValue() + outro;
  }
}

Somador<Integer> s = new Somador<>(10);
System.out.println(s.somar(5.5));

Linha 1: T extends Number restringe o tipo a subclasses de Number.
Linha 9: Pode-se usar métodos de Number como doubleValue().

Exemplos incorretos e pegadinhas

💥Exemplo 1: Tentar usar tipo primitivo

Caixa<int> caixa = new Caixa<>();

Erro – não se pode usar tipos primitivos com generics. Use Integer em vez de int.

💥Exemplo 2: Usar genéricos em contexto estático

public class Exemplo<T> {
  private T valor;

  public static void imprimir(T valor) {
    System.out.println(valor);
  }
}

Erro – métodos static não têm acesso ao tipo genérico da instância T.

💥Exemplo 3: Uso indevido de cast

List lista = new ArrayList();
lista.add("texto");
Integer numero = (Integer) lista.get(0); //❌

🧨 Explicação do c´ódigo acima:
Linha 1: A lista é criada sem usar generics. Ou seja, é uma List “crua” (raw type), onde qualquer tipo de objeto pode ser adicionado.
Linha 2: Adicionamos uma String com valor "texto".
Linha 3: Tentamos recuperar esse valor usando um cast explícito para Integer.

🔴 Isso compila, mas em tempo de execução ocorre:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

✅ Exemplo correto com generics:

List<String> lista = new ArrayList<>();
lista.add("texto");
String texto = lista.get(0);

🟢 O que muda aqui?

Linha 1: A lista é declarada como List<String>, ou seja, só aceita String
Linha 2: Adicionamos uma String, o que está OK.
Linha 3: Ao chamar get(0), o compilador já sabe que o retorno será String, então não há necessidade de cast.

🛡️ Vantagens:

O compilador impede adicionar objetos que não sejam do tipo String.

Sem necessidade de cast ao recuperar o valor.

Mais seguro e evita erros em tempo de execução.

⚠️ Pegadinha comum na prova

Você verá algo assim:

List lista = new ArrayList();
lista.add(100);
String valor = (String) lista.get(0);

Isso compila, mas causa erro em tempo de execução (ClassCastException) porque 100 é Integer, e o cast tenta forçar String.

Hora do simulado

🧩Clique na pergunta para ver as respostas.
1️⃣ Qual das alternativas cria corretamente uma instância da classe genérica Caixa<T>?

✅Resposta: A
A) ✅ Válido – tipo genérico declarado corretamente.
B) ❌ Gera unchecked warning (embora compile).
C) ❌ Também compila, mas sem informar o tipo explícito — unsafe.
D) ❌ Nem todas estão corretas, logo não é a certa.

A) Caixa<String> c = new Caixa<String>();
B) Caixa<String> c = new Caixa();
C) Caixa c = new Caixa<>();
D) Todas as anteriores

2️⃣O que ocorre se tentar usar int como tipo genérico?

✅ Resposta: C
Genéricos não aceitam tipos primitivos, como int, double, char etc.
Deve-se usar Integer, Double, Character (wrappers).

A) O código compila normalmente
B) int será convertido para Integer automaticamente
C) Erro de compilação
D) Erro em tempo de execução

3️⃣Qual das opções abaixo define uma classe genérica com dois parâmetros de tipo?

B) ✅ Correto – class Par {} define dois tipos.
A) ❌ Só define um tipo.
C) ❌ Falta vírgula entre os tipos.
D) ❌ Uso de parênteses não é válido na declaração da classe.

A) class Par<X> {}
B) class Par<K, V> {}
C) class Par<K V> {}
D) class Par(K, V) {}

4️⃣Sobre genéricos e métodos estáticos, é correto afirmar:

✅Gabarito: C – Métodos estáticos devem declarar seus próprios parâmetros de tipo genérico.
Métodos estáticos não podem acessar o tipo genérico da classe.
Mas podem usar generics próprios, assim:

public static <T> void imprimir(T valor) {
  System.out.println(valor);
}

A) Métodos estáticos podem usar o tipo genérico da classe
B) Métodos estáticos não podem usar tipos genéricos
C) Métodos estáticos devem declarar seus próprios parâmetros de tipo genérico
D) Tipos genéricos não funcionam em métodos

5️⃣O que o código abaixo imprime?

✅Resposta: A
Chave = “idade”, valor = 30.
Impressão será: idade-30

Par<String, Integer> p = new Par<>("idade", 30);
System.out.println(p.getChave() + "-" + p.getValor());

A) idade-30
B) 30-idade
C) idade
D) 30

🧠 Resumo final do tópico: Generic Classes

  • Genéricos permitem reuso e segurança de tipo em tempo de compilação.
  • Declaração comum: class Nome<T> { ... }
  • Você pode usar múltiplos parâmetros de tipo como T, U, K, V.
  • Primitivos não são permitidos – use wrappers (Integer, Double, etc).
  • Métodos static não podem acessar os parâmetros genéricos da classe diretamente.
  • Bounded types (T extends Tipo) permitem restringir os tipos aceitos.