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+.


🔥 1. Uso de Generics con Interfaces Funcionales

Podemos combinar Generics con Interfaces Funcionales para hacer código más flexible y reutilizable.

✅ Ejemplo: Función genérica con Function<T, R>

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);
    }
}

Uso:

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.


🔄 2. Generics con Reflection

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.

✅ Ejemplo: Obtener el tipo genérico en runtime

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());
        }
    }
}

Uso:

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.


🏗️ 3. Generics Anidados

Puedes anidar Generics para hacer estructuras más avanzadas.

✅ Ejemplo: Par de Valores Genérico

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;
    }
}

✅ Ejemplo: Uso con Generics Anidados

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.


🏹 4. Covarianza y Contravarianza con Wildcards (?)

Covarianza (? extends T)

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);
        }
    }
}

Uso:

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).


Contravarianza (? super T)

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);
    }
}

Uso:

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.


🚀 5. Generics con Records (Java 14+)

Los record de Java reducen el código repetitivo en clases de solo datos.

✅ Ejemplo: Record Genérico

public record Par<T, U>(T primero, U segundo) {}

Uso:

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().


🎯 6. Builder Pattern con Generics

Podemos usar Generics para hacer un Builder Pattern flexible.

✅ Ejemplo: Builder Genérico

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;
    }
}

Uso:

String resultado = new Constructor<String>().setValor("Hola").build();
System.out.println(resultado); // "Hola"

Ventaja: Podemos construir diferentes objetos sin modificar el Builder.


🔥 7. Generics con Enums

Puedes usar Generics en Enums para mejorar la seguridad de tipos.

✅ Ejemplo: Enum con Generics

public enum Estado<T> {
    ACTIVO(true), INACTIVO(false);

    private final T valor;

    Estado(T valor) {
        this.valor = valor;
    }

    public T getValor() {
        return valor;
    }
}

Uso:

Estado<Boolean> estado = Estado.ACTIVO;
System.out.println(estado.getValor()); // true

Ventaja: Permite asignar valores específicos a cada estado.


🎯 Conclusión

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. 🚀

⚠️ **GitHub.com Fallback** ⚠️