Introducción a los genéricos
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 53 de 87.
Imagina que necesitas crear una clase que almacene un valor y lo recupere más tarde. Sin genéricos, tendrías que escribir clases separadas para cada tipo: una para Integer, una para String, una para Double, y así sucesivamente. Esto conduce a la duplicación de código y a dolores de cabeza en el mantenimiento.
Los genéricos resuelven este problema al permitirte escribir clases y métodos que funcionen con cualquier tipo, manteniendo al mismo tiempo la seguridad de tipos. Defines un marcador de posición (llamado parámetro de tipo) que se reemplaza por un tipo real cuando utilizas la clase:
// Sin genéricos - usa Object, requiere conversión
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Se requiere conversión manual
// Con genéricos - seguro para tipos, sin conversión
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // No se requiere conversiónLos corchetes angulares <T> denotan un parámetro de tipo. Cuando escribes List<String>, le estás diciendo al compilador que esta lista solo contendrá cadenas. Si intentas añadir un entero, el compilador detecta el error inmediatamente en lugar de fallar en tiempo de ejecución.
Los genéricos proporcionan dos beneficios clave: eliminan la necesidad de realizar conversiones explícitas y detectan errores de tipo en tiempo de compilación en lugar de en tiempo de ejecución. Esto hace que su código sea más seguro y limpio. En las siguientes lecciones, exploraremos cómo crear sus propias clases y métodos genéricos.
Desafío
FácilVamos a construir un sistema de almacenamiento simple que demuestre el poder de los genéricos comparando la forma antigua (usando Object con casting) frente a la forma nueva (usando genéricos con seguridad de tipos).
Crearás tres archivos para ver la diferencia de primera mano:
OldBox.java: Crea una clase que almacene valores al estilo anterior a los genéricos. Tu OldBox debe tener un campo privadocontentde tipoObject. Incluye un constructor que acepte un Object y lo almacene, y un métodogetContent()que devuelva el Object. Este enfoque funciona pero requiere casting al recuperar los valores.GenericBox.java: Ahora crea la versión moderna y segura en cuanto a tipos usando genéricos. Tu GenericBox debe usar un parámetro de tipoTen su declaración. Necesita un campo privadocontentde tipoT, un constructor que acepte un valorT, y un métodogetContent()que devuelva el tipoT. ¡No se necesita casting al usar esta clase!Main.java: Demuestra ambos enfoques uno al lado del otro. Recibirás dos entradas: una palabra (String) y un número (entero).Primero, usa el enfoque antiguo: crea un OldBox que almacene la palabra, luego recupérala con un cast a String e imprime:
OldBox (with cast): [value]A continuación, usa el enfoque genérico: crea un
GenericBox<String>que almacene la misma palabra, recupérala sin casting e imprime:GenericBox (no cast): [value]Luego muestra los genéricos funcionando con enteros: crea un
GenericBox<Integer>que almacene el número, recupéralo e imprime:GenericBox Integer: [value]Finalmente, imprime una línea en blanco seguida de:
Type safety: Generics catch errors at compile time!
Recibirás dos entradas en orden: una palabra (String) y un número (entero como String, que deberás parsear).
Observa la diferencia clave: con OldBox, debes hacer un cast (String) box.getContent() y esperar haber acertado con el tipo. Con GenericBox, el compilador conoce el tipo y no es necesario realizar ningún casting. ¡Este es el beneficio principal de los genéricos: seguridad de tipos sin la molestia del casting!
Hoja de referencia
Los genéricos le permiten escribir clases y métodos que funcionan con cualquier tipo manteniendo la seguridad de tipos. Utilizan parámetros de tipo (marcadores de posición) que se reemplazan con tipos reales cuando se utiliza la clase.
Sin genéricos (usando Object):
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manual casting requiredCon genéricos (seguridad de tipos):
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // No casting neededCreación de una clase genérica:
public class GenericBox<T> {
private T content;
public GenericBox(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}Uso de una clase 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-safeLos corchetes angulares <T> denotan un parámetro de tipo. Los beneficios incluyen: no se requiere casting explícito y los errores de tipo se detectan en tiempo de compilación en lugar de en tiempo de ejecución.
Pruébalo tú mismo
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: Crear un OldBox que almacene la palabra
// Recuperar con un cast a String e imprimir: "OldBox (with cast): [value]"
// TODO: Crear un GenericBox<String> que almacene la palabra
// Recuperar sin casting e imprimir: "GenericBox (no cast): [value]"
// TODO: Crear un GenericBox<Integer> que almacene el número
// Recuperar e imprimir: "GenericBox Integer: [value]"
// TODO: Imprimir una línea en blanco, luego imprimir:
// "Type safety: Generics catch errors at compile time!"
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Iterator