Genéricos en Java: Uso y Características Fundamentales

Declaración y Letras Comunes

La sintaxis básica para una clase genérica es:

class NombreClase<ListaLetras> {
    // Miembros de la clase
}

Las letras comunes incluyen T para tipo, K y V para clave-valor, E para elemento, y ? para tipo desconocido. No se pueden usar en atributos o métodos estáticos, ya que el tipo se determina en el momento de la instanciación, mientras que los miembros estáticos se resuelven en tiempo de compilación.

Uso Práctico

Al utilizar genéricos, se debe especificar un tipo concreto (solo tipos de referencia, no primitivos). Esto garantiza la verificación de tipos en compilación y elimina la necesidad de conversiones explícitas.

Ejemplos Básicos

Clase genérica simple:

package ejemplo.generics;

public class Estudiante<T1, T2> {
    private T1 notaProgramacion;
    private T2 notaBasesDatos;

    public T1 obtenerNotaProgramacion() {
        return notaProgramacion;
    }

    public void establecerNotaProgramacion(T1 nota) {
        this.notaProgramacion = nota;
    }

    public T2 obtenerNotaBasesDatos() {
        return notaBasesDatos;
    }

    public void establecerNotaBasesDatos(T2 nota) {
        this.notaBasesDatos = nota;
    }

    public static void main(String[] args) {
        Estudiante<String, Double> est = new Estudiante<>();
        est.establecerNotaProgramacion("Sobresaliente");
        double notaBD = est.obtenerNotaBasesDatos(); // Conversión automática
    }
}

Clase genérica con restricciones:

package ejemplo.generics;

import java.util.ArrayList;
import java.util.List;

public class RepositorioDatos<T> {
    public void agregar(T elemento) {
        // Lógica para agregar
    }

    public T buscarPorId(int id) {
        return null; // Lógica de búsqueda
    }

    public List<T> buscarPorCondicion(String condicion) {
        return new ArrayList<>();
    }

    // Nota: Los métodos estáticos no pueden usar el parámetro de tipo T de la clase
    public static <U> void actualizar(U elemento) {
        // Lógica estática con tipo genérico propio
    }
}

Interfaz genérica:

package ejemplo.generics;

public interface Comparador<T> {
    void comparar(T objeto);
}

Método genérico: se declara el parámetro de tipo antes del tipo de retorno.

package ejemplo.generics;

import java.io.Closeable;
import java.io.IOException;

public class PruebaMetodos {
    public static void main(String[] args) {
        probar("texto"); // T es String
        probar(10);       // T es Integer
    }

    public static <T> void probar(T valor) {
        System.out.println(valor);
    }

    // Método genérico con restricción
    public static <T extends Closeable> void cerrarRecursos(T... recursos) {
        for (T recurso : recursos) {
            try {
                if (recurso != null) recurso.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Genéricos con Límites y Comodines

Los comodines (wildcards) permiten mayor flexibilidad. ? indica tipo desconocido, ? extends para límite superior, y ? super para límite inferior.

package ejemplo.generics;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class PruebaComodines {
    public static void main(String[] args) {
        List<Double> listaDecimales = new ArrayList<>();
        ordenarLista(listaDecimales);

        List<Integer> listaEnteros = new ArrayList<>();
        listaEnteros.add(5);
        listaEnteros.add(10);

        Filtro<Number> filtro = n -> n.doubleValue() > 0;
        eliminarSi(listaEnteros, filtro);
    }

    // ? extends para límite superior: acepta Number o subclases
    public static void ordenarLista(List<? extends Number> lista) {
        if (lista == null || lista.isEmpty()) return;
        Collections.sort(lista, (a, b) -> Double.compare(a.doubleValue(), b.doubleValue()));
    }

    interface Filtro<E> {
        boolean evaluar(E elemento);
    }

    // ? super para límite inferior: acepta Number o superclases
    public static <E> List<E> eliminarSi(List<E> lista, Filtro<? super E> filtro) {
        List<E> removidos = new ArrayList<>();
        for (E elemento : lista) {
            if (filtro.evaluar(elemento)) {
                removidos.add(elemento);
            }
        }
        lista.removeAll(removidos);
        return removidos;
    }
}

Genéricos acotados con múltiples restriccionse:

package ejemplo.generics;

public class EjemploAcotado {
    public static void main(String[] args) {
        Impresora<Coche> impreCoche = new Impresora<>(new Coche());
        impreCoche.imprimir();

        Impresora<Autobus> impreBus = new Impresora<>(new Autobus());
        impreBus.imprimir();
    }
}

// T debe extender de Vehiculo e implementar Identificable
class Impresora<T extends Vehiculo & Identificable> {
    private T contenido;

    Impresora(T contenido) {
        this.contenido = contenido;
    }

    public void imprimir() {
        System.out.println("Marca: " + contenido.obtenerMarca());
        System.out.println(contenido);
    }
}

class Vehiculo implements Identificable {
    String marca;
    String color;

    @Override
    public String obtenerMarca() {
        return this.marca;
    }

    @Override
    public String obtenerColor() {
        return this.color;
    }
}

class Autobus extends Vehiculo {}

class Coche extends Vehiculo {}

interface Identificable {
    String obtenerMarca();
    String obtenerColor();
}

Herencia e Implementación con Genéricos

Las subclases deben mantener o ampliar los parámetros de tipo del padre.

package ejemplo.generics;

public abstract class PadreGenerico<T, U> {
    T nombre;
    public abstract void procesar(T dato);
}

class HijaConcreta extends PadreGenerico<String, Integer> {
    @Override
    public void procesar(String dato) {
        // Implementación
    }
}

class HijaGenerica<T, U> extends PadreGenerico<T, U> {
    @Override
    public void procesar(T dato) {
        // Implementación
    }
}

// Borrado de tipos: se usa Object
class HijaBorrada<U> extends PadreGenerico {
    @Override
    public void procesar(Object dato) {
        // Implementación
    }
}

Interfaces genéricas similares:

package ejemplo.generics;

public interface ComparablePersonalizado<T> {
    void comparar(T otro);
}

class ComparadorString implements ComparablePersonalizado<String> {
    @Override
    public void comparar(String otro) {
        // Implementación
    }
}

class ComparadorBorrado implements ComparablePersonalizado {
    @Override
    public void comparar(Object otro) {
        // Implementación
    }
}

Borrado de Tipos (Erasure)

En Java, los genéricos se implementan mediante borrado de tipos en tiempo de compilación. Esto significa que la información de tipo se elimina al generar bytecode, reemplazándose por Object o límites inferiores. Por ejemplo, después del borrado, una clase genérica como List<String> se convierte en List en el bytecode. Esto impide sobrecargas basadas en parámetros de tipo diferentes.

Reflexión para Obtener Tipos Genéricos

Aunque el borrado elimina tipos en tiempo de ejecución, se puede usar reflexión para recuperar tipos genéricos en ciertos contextos, como parámetros de método.

package ejemplo.generics;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;

public class ReflexionGenericos {
    public static void main(String[] args) throws NoSuchMethodException {
        Method metodo = ReflexionGenericos.class.getMethod("procesarLista", List.class);
        Type[] tiposParametro = metodo.getGenericParameterTypes();
        ParameterizedType tipoParametrizado = (ParameterizedType) tiposParametro[0];
        System.out.println("Tipo crudo: " + tipoParametrizado.getRawType());
        System.out.println("Tipo real: " + tipoParametrizado.getActualTypeArguments()[0]);
    }

    public static void procesarLista(List<Date> lista) {
        // Método que usa List<date> para recuperar tipo vía reflexión
    }
}</date>

Salida esperada: Tipo crudo: interface java.util.Tipo real: class java.util.Date

Etiquetas: java generics type-safety erasure wildcards

Publicado el 7-25 19:03