02 ‐ Ejemplos avanzados - Cyb3rh4ck/java-core-fundamentals GitHub Wiki
¡Vamos a llevar los Generics al siguiente nivel! 🚀 Aquí tienes ejemplos avanzados, incluyendo uso con interfaces funcionales, reflection, generics anidados y más en JDK 17+.
Podemos combinar Generics con Interfaces Funcionales para hacer código más flexible y reutilizable.
import java.util.function.Function;
public class Transformador<T, R> {
private Function<T, R> transformador;
public Transformador(Function<T, R> transformador) {
this.transformador = transformador;
}
public R aplicar(T valor) {
return transformador.apply(valor);
}
}
Transformador<String, Integer> parseador = new Transformador<>(Integer::parseInt);
System.out.println(parseador.aplicar("123")); // Salida: 123 (Integer)
Transformador<Double, String> formateador = new Transformador<>(d -> "Valor: " + d);
System.out.println(formateador.aplicar(3.14)); // Salida: "Valor: 3.14"
✅ Ventaja: Podemos reutilizar la clase con distintos tipos de transformación.
Usar Reflection con Generics es un reto porque Java usa Type Erasure (borra información de tipos en tiempo de ejecución), pero podemos obtener información de tipos en runtime con ParameterizedType
.
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class TipoGenerico<T> {
public void obtenerTipo() {
Type superClase = getClass().getGenericSuperclass();
if (superClase instanceof ParameterizedType parameterizedType) {
Type tipoReal = parameterizedType.getActualTypeArguments()[0];
System.out.println("Tipo en tiempo de ejecución: " + tipoReal.getTypeName());
}
}
}
class MiClase extends TipoGenerico<String> {}
MiClase instancia = new MiClase();
instancia.obtenerTipo(); // Salida: Tipo en tiempo de ejecución: java.lang.String
✅ Ventaja: Permite descubrir el tipo de una subclase que extiende una clase genérica.
Puedes anidar Generics para hacer estructuras más avanzadas.
public class Par<A, B> {
private A primero;
private B segundo;
public Par(A primero, B segundo) {
this.primero = primero;
this.segundo = segundo;
}
public A getPrimero() {
return primero;
}
public B getSegundo() {
return segundo;
}
}
Par<String, Par<Integer, Double>> datos = new Par<>("Número", new Par<>(10, 3.14));
System.out.println(datos.getPrimero()); // "Número"
System.out.println(datos.getSegundo().getPrimero()); // 10
System.out.println(datos.getSegundo().getSegundo()); // 3.14
✅ Ventaja: Permite crear estructuras de datos complejas con facilidad.
Permite leer datos de una colección genérica de subtipos de T
.
import java.util.List;
public class Util {
public static void imprimirLista(List<? extends Number> lista) {
for (Number num : lista) {
System.out.println(num);
}
}
}
List<Integer> enteros = List.of(1, 2, 3);
List<Double> decimales = List.of(1.1, 2.2, 3.3);
Util.imprimirLista(enteros); // ✅ Funciona
Util.imprimirLista(decimales); // ✅ Funciona
🚀 Explicación:
✅ ? extends Number
significa que puede recibir cualquier subclase de Number
.
❌ No permite modificar la lista (por seguridad de tipos).
Permite modificar una colección genérica de un supertipo de T
.
import java.util.List;
public class Util {
public static void agregarElemento(List<? super Integer> lista) {
lista.add(100);
System.out.println(lista);
}
}
List<Number> numeros = new ArrayList<>();
Util.agregarElemento(numeros); // ✅ Funciona porque Number es supertipo de Integer
🚀 Explicación:
✅ ? super Integer
significa que la lista puede ser de Integer, Number o Object.
✅ Se permite agregar elementos de tipo Integer
.
Los record
de Java reducen el código repetitivo en clases de solo datos.
public record Par<T, U>(T primero, U segundo) {}
Par<String, Integer> resultado = new Par<>("Edad", 25);
System.out.println(resultado); // Par[primero=Edad, segundo=25]
✅ Ventaja: record
maneja automáticamente toString()
, equals()
y hashCode()
.
Podemos usar Generics para hacer un Builder Pattern flexible.
public class Constructor<T> {
private T valor;
public Constructor<T> setValor(T valor) {
this.valor = valor;
return this; // Retorna la propia instancia
}
public T build() {
return valor;
}
}
String resultado = new Constructor<String>().setValor("Hola").build();
System.out.println(resultado); // "Hola"
✅ Ventaja: Podemos construir diferentes objetos sin modificar el Builder
.
Puedes usar Generics en Enums para mejorar la seguridad de tipos.
public enum Estado<T> {
ACTIVO(true), INACTIVO(false);
private final T valor;
Estado(T valor) {
this.valor = valor;
}
public T getValor() {
return valor;
}
}
Estado<Boolean> estado = Estado.ACTIVO;
System.out.println(estado.getValor()); // true
✅ Ventaja: Permite asignar valores específicos a cada estado.
Estos son algunos usos avanzados de los Generics en Java (JDK 17+):
✅ Interfaces funcionales con Generics.
✅ Reflection para obtener el tipo real en runtime.
✅ Anidación de Generics para estructuras complejas.
✅ Covarianza (extends
) y Contravarianza (super
) para flexibilidad en listas.
✅ Uso con record
, enum
y Builder Pattern
.
Si quieres más ejemplos o una explicación detallada de alguno, dime. 🚀