Explorando a API Optional no Java
em 14 de Janeiro de 2025
Os Generics foram introduzidos no Java 5 para permitir que classes, interfaces e métodos trabalhem com tipos parametrizados. Essa funcionalidade aumenta a segurança e a reutilização do código, reduzindo a necessidade de conversões explícitas e evitando erros em tempo de execução.
Neste artigo, exploraremos o conceito de Generics, como utilizá-los e como eles tornam o código mais robusto e flexível.
Por Que Usar Generics?
Antes dos Generics, os desenvolvedores usavam coleções que armazenavam objetos como Object
. Isso exigia type casting ao recuperar elementos, o que era propenso a erros:
Sem Generics:
List lista = new ArrayList();
lista.add("Texto");
String s = (String) lista.get(0); // Casting necessário
Se um tipo incompatível fosse adicionado à lista, isso causaria um erro em tempo de execução:
lista.add(123); // Compila, mas lança ClassCastException ao recuperar
Com Generics, o tipo é definido na declaração, eliminando a necessidade de casts explícitos e aumentando a segurança:
Com Generics:
List<String> lista = new ArrayList<>();
lista.add("Texto");
String s = lista.get(0); // Sem casting
Como Funciona o Generics
Os Generics funcionam parametrizando o tipo em tempo de compilação. Isso significa que você pode especificar o tipo ao criar uma classe, interface ou método genérico.
public class Caixa<T> {
private T objeto;
public void set(T objeto) {
this.objeto = objeto;
}
public T get() {
return objeto;
}
}
public class Main {
public static void main(String[] args) {
Caixa<String> caixa = new Caixa<>();
caixa.set("Olá, Generics!");
System.out.println(caixa.get()); // Saída: Olá, Generics!
}
}
public interface Comparador<T> {
int comparar(T o1, T o2);
}
public class ComparadorString implements Comparador<String> {
@Override
public int comparar(String o1, String o2) {
return o1.compareTo(o2);
}
}
Métodos Genéricos
Você também pode criar métodos genéricos que funcionam com diferentes tipos, sem precisar parametrizar a classe inteira.
Exemplo:
public class Util {
public static <T> void imprimirArray(T[] array) {
for (T elemento : array) {
System.out.println(elemento);
}
}
public static void main(String[] args) {
Integer[] numeros = {1, 2, 3};
String[] palavras = {"A", "B", "C"};
Util.imprimirArray(numeros);
Util.imprimirArray(palavras);
}
}
Wildcards (Curingas)
O wildcard (?
) é usado quando você não sabe ou não precisa especificar o tipo exato em um parâmetro genérico.
Unbounded Wildcard (?
): Aceita qualquer tipo.
public void imprimirLista(List<?> lista) {
for (Object elemento : lista) {
System.out.println(elemento);
}
}
Upper Bounded Wildcard (<? extends T>
): Permite tipos que são subtipos de T
.
public void imprimirNumeros(List<? extends Number> numeros) {
for (Number numero : numeros) {
System.out.println(numero);
}
}
Lower Bounded Wildcard (<? super T>
): Permite tipos que são supertipos de T
.
public void adicionarElemento(List<? super Integer> lista) {
lista.add(123);
}
Restrições e Limitações
Tipos Primitivos: Generics não funcionam diretamente com tipos primitivos como int
ou double
. Use as classes wrapper (Integer
, Double
) em vez disso.
List<Integer> lista = new ArrayList<>();
Erasure (Apagamento de Tipos): Durante a compilação, os tipos genéricos são apagados e substituídos por Object
. Isso significa que:
Benefícios dos Generics
ClassCastException
em tempo de execução.Os Generics tornam o Java mais seguro e flexível, permitindo que desenvolvedores criem código reutilizável e menos propenso a erros. Ao entender como funcionam e explorar seus recursos, você pode melhorar a qualidade e a robustez de seus projetos. Experimente implementar Generics em suas classes e métodos para descobrir o quanto eles podem simplificar e aprimorar seu código!