Introdução a Generics
Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 53 de 87.
Imagine que você precise criar uma classe que armazena um valor e o recupera mais tarde. Sem generics, você teria que escrever classes separadas para cada tipo — uma para Integer, uma para String, uma para Double, e assim por diante. Isso leva à duplicação de código e dores de cabeça na manutenção.
Generics resolvem esse problema permitindo que você escreva classes e métodos que funcionam com qualquer tipo, mantendo a segurança de tipos. Você define um marcador (chamado de parâmetro de tipo) que é substituído por um tipo real quando você usa a classe:
// Sem generics - usa Object, requer casting
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Casting manual necessário
// Com generics - type-safe, sem casting
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // Sem necessidade de castingOs colchetes angulares <T> denotam um parâmetro de tipo. Quando você escreve List<String>, você está dizendo ao compilador que esta lista conterá apenas strings. Se você tentar adicionar um número inteiro, o compilador captura o erro imediatamente, em vez de falhar em tempo de execução.
Generics oferecem dois benefícios principais: eles eliminam a necessidade de casting explícito e detectam erros de tipo em tempo de compilação, em vez de tempo de execução. Isso torna seu código mais seguro e limpo. Nas lições a seguir, exploraremos como criar suas próprias classes e métodos genéricos.
Desafio
FácilVamos construir um sistema de armazenamento simples que demonstra o poder dos generics comparando a maneira antiga (usando Object com casting) versus a nova maneira (usando generics com segurança de tipos).
Você criará três arquivos para ver a diferença em primeira mão:
OldBox.java: Crie uma classe que armazena valores da maneira anterior aos generics. Sua OldBox deve ter um campo privadocontentdo tipoObject. Inclua um construtor que aceite um Object e o armazene, e um métodogetContent()que retorne o Object. Essa abordagem funciona, mas requer casting ao recuperar os valores.GenericBox.java: Agora crie a versão moderna e segura quanto ao tipo usando generics. Sua GenericBox deve usar um parâmetro de tipoTem sua declaração. Ela precisa de um campo privadocontentdo tipoT, um construtor que aceite um valorTe um métodogetContent()que retorne o tipoT. Nenhum casting é necessário ao usar esta classe!Main.java: Demonstre ambas as abordagens lado a lado. Você receberá duas entradas: uma palavra (String) e um número (inteiro).Primeiro, use a abordagem antiga: crie uma OldBox armazenando a palavra, depois recupere-a com um cast para String e imprima:
OldBox (with cast): [value]Em seguida, use a abordagem genérica: crie um
GenericBox<String>armazenando a mesma palavra, recupere-a sem casting e imprima:GenericBox (no cast): [value]Depois, mostre os generics funcionando com inteiros: crie um
GenericBox<Integer>armazenando o número, recupere-o e imprima:GenericBox Integer: [value]Por fim, imprima uma linha em branco seguida por:
Type safety: Generics catch errors at compile time!
Você receberá duas entradas em ordem: uma palavra (String) e um número (inteiro como String, que você deverá converter).
Observe a diferença fundamental: com OldBox, você deve fazer o cast (String) box.getContent() e torcer para ter acertado o tipo. Com GenericBox, o compilador conhece o tipo e nenhum casting é necessário. Este é o benefício central dos generics — segurança de tipos sem o incômodo do casting!
Folha de consulta
Generics permitem que você escreva classes e métodos que funcionam com qualquer tipo, mantendo a segurança de tipos (type safety). Eles usam parâmetros de tipo (placeholders) que são substituídos pelos tipos reais quando a classe é utilizada.
Sem generics (usando Object):
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manual casting requiredCom generics (type-safe):
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // No casting neededCriando uma classe genérica:
public class GenericBox<T> {
private T content;
public GenericBox(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}Usando uma classe genérica:
GenericBox<String> stringBox = new GenericBox<String>("Hello");
String value = stringBox.getContent(); // No casting
GenericBox<Integer> intBox = new GenericBox<Integer>(42);
Integer number = intBox.getContent(); // Type-safeOs colchetes angulares <T> denotam um parâmetro de tipo. Os benefícios incluem: nenhum casting explícito é necessário e os erros de tipo são capturados em tempo de compilação em vez de tempo de execução.
Experimente você mesmo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String word = scanner.nextLine();
int number = Integer.parseInt(scanner.nextLine());
// TODO: Criar um OldBox armazenando a word
// Recuperar com cast para String e imprimir: "OldBox (with cast): [value]"
// TODO: Criar um GenericBox<String> armazenando a word
// Recuperar sem casting e imprimir: "GenericBox (no cast): [value]"
// TODO: Criar um GenericBox<Integer> armazenando o number
// Recuperar e imprimir: "GenericBox Integer: [value]"
// TODO: Imprimir uma linha em branco, depois imprimir:
// "Type safety: Generics catch errors at compile time!"
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave thisMétodosCampos (Atributos)Método ConstrutorSobrecarga de ConstrutorRecapitulação - Calculadora Simples4Herança
Herança Básica (extends)A Palavra-chave superSobrescrita de Método (@Override)Encadeamento de ConstrutoresA Classe ObjectHerança Simples e MultinívelPor que não há Herança Múltipla de ClassesRecapitulação - Hierarquia de Funcionários7Métodos Especiais e a Classe Object
Método toString()equals() e hashCode()Método clone()compareTo() e ComparableInterface ComparatorRecapitulação - Ordenação Personalizada2Modificadores de Acesso e Encapsulamento
Visão Geral dos Níveis de AcessoMétodos Getter e SetterOcultação de InformaçõesA Palavra-chave finalRecapitulação - Gerenciador de Conta Bancária5Polimorfismo
Fundamentos de Sobrecarga de MétodosSobrescrita de Métodos (Tempo de Execução)Upcasting e DowncastingO Operador instanceofClasses e Métodos AbstratosRecapitulação - Calculadora de Formas8Conceitos Avançados de OOP
Composição vs HerançaAgregação vs ComposiçãoClasses Internas, Aninhadas e AnônimasEnums e Métodos de EnumRecords (Java 16+)Classes Seladas (Java 17+)11Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão BuilderPadrão ObserverPadrão Strategy3Propriedades de Classe e Membros Estáticos
Variáveis de Instância vs EstáticasMétodos EstáticosBlocos EstáticosConstantes (static final)Recapitulação - Contador e Utilitário6Interfaces e Classes Abstratas
Introdução às InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesDefault e Static em InterfacesClasses Abstratas vs InterfacesInterfaces FuncionaisRecapitulação - Sistema de Pagamento9Generics
Introdução a GenericsClasses GenéricasMétodos GenéricosParâmetros de Tipos DelimitadosWildcards (?, extends, super)Recapitulação - Container Genérico12Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Iterator