Java
Tutorial Java: Generics
Java Generics: Aprende los fundamentos de clases e interfaces genéricas para mejorar la flexibilidad y seguridad de tus aplicaciones.
Aprende Java y certifícateFundamentos de generics: clases e interfaces genéricas
Los generics sirven para crear clases, interfaces y métodos que operan con tipos parametrizados. Fueron introducidos en Java 5 para dar seguridad de tipos en tiempo de compilación y eliminar la necesidad de realizar conversiones explícitas de tipos (casting).
La idea fundamental detrás de los generics es permitir que los tipos (clases e interfaces) sean parámetros al definir clases, interfaces y métodos. Usando generics, se puede crear una única clase que funcione automáticamente con diferentes tipos de datos.
Una clase genérica se define utilizando uno o más parámetros de tipo entre ángulos (<>
). La convención de nomenclatura sugiere usar letras mayúsculas simples como:
T
- TipoE
- ElementoK
- ClaveV
- ValorN
- Número
Por ejemplo, una clase genérica simple se podría definir así:
public class Contenedor<T> {
private T valor;
public Contenedor(T valor) {
this.valor = valor;
}
public T obtener() {
return valor;
}
public void establecer(T nuevoValor) {
this.valor = nuevoValor;
}
}
Al instanciar esta clase, se proporciona un tipo real que reemplaza el parámetro T
:
// Contenedor para Strings
Contenedor<String> contenedorTexto = new Contenedor<>("Hola");
String texto = contenedorTexto.obtener(); // No se necesita casting
// Contenedor para Integers
Contenedor<Integer> contenedorNumero = new Contenedor<>(42);
int numero = contenedorNumero.obtener(); // No se necesita casting
Se pueden definir clases genéricas con múltiples parámetros de tipo:
public class Par<K, V> {
private K clave;
private V valor;
public Par(K clave, V valor) {
this.clave = clave;
this.valor = valor;
}
public K getClave() {
return clave;
}
public V getValor() {
return valor;
}
}
Los parámetros de tipo pueden ser acotados (bounded), lo que restringe los tipos que pueden usarse como argumentos:
// T debe ser Number o una subclase de Number
public class Calculadora<T extends Number> {
private T valor;
public Calculadora(T valor) {
this.valor = valor;
}
public double obtenerValorDoble() {
return valor.doubleValue();
}
}
Este ejemplo restringe el parámetro T
a Number
o cualquiera de sus subclases (como Integer
, Double
, etc.).
También se pueden definir interfaces genéricas:
public interface Comparable<T> {
int compareTo(T other);
}
Una clase que implementa una interfaz genérica debe especificar un tipo concreto o mantener el parámetro genérico:
// Implementación con tipo concreto
public class Persona implements Comparable<Persona> {
private String nombre;
public Persona(String nombre) {
this.nombre = nombre;
}
@Override
public int compareTo(Persona otra) {
return this.nombre.compareTo(otra.nombre);
}
}
// Implementación manteniendo el parámetro genérico
public class ListaOrdenada<E extends Comparable<E>> {
// Implementación
}
Los generics también admiten limitaciones múltiples utilizando el operador &
:
// T debe implementar Comparable e Iterable
public class Ejemplo<T extends Comparable<T> & Iterable<?>> {
// Implementación
}
La herencia funciona de manera especial con las clases genéricas. Si Hijo
extiende Padre
, entonces Contenedor<Hijo>
no es subclase de Contenedor<Padre>
. Esta relación se conoce como invarianza de los generics.
// Esto NO compila
Contenedor<Number> numeros = new Contenedor<Integer>(10); // Error
// Esto sí compila
Contenedor<Integer> enteros = new Contenedor<>(10);
Contenedor<? extends Number> numeros = enteros; // Usando wildcard
Las clases genéricas anidadas pueden acceder a los parámetros de tipo de la clase contenedora:
public class Exterior<T> {
private T dato;
public class Interior {
public T obtenerDatoExterior() {
return dato; // Accede al parámetro T de la clase Exterior
}
}
}
Métodos genéricos y wildcard types (? extends, ? super)
Los métodos genéricos permiten definir parámetros de tipo a nivel de método, sin necesidad de que toda la clase sea genérica.
La sintaxis básica de un método genérico es la siguiente:
public <T> T metodoGenerico(T parametro) {
return parametro;
}
El parámetro de tipo <T>
se declara antes del tipo de retorno para poder utilizar el mismo tipo tanto para el parámetro como para el valor de retorno, o incluso para variables locales dentro del método.
Los métodos genéricos se pueden llamar de dos formas:
// Forma explícita (especificando el tipo)
String resultado = this.<String>metodoGenerico("Hola");
// Forma implícita (inferencia de tipo)
String resultado = metodoGenerico("Hola");
En la mayoría de los casos, el compilador de Java puede inferir el tipo a partir de los argumentos proporcionados.
Un caso de uso habitual de los métodos genéricos es crear métodos de utilidad que funcionen con diferentes tipos:
public static <E> void imprimirArray(E[] array) {
for (E elemento : array) {
System.out.print(elemento + " ");
}
System.out.println();
}
// Uso
Integer[] numeros = {1, 2, 3, 4, 5};
String[] palabras = {"Hola", "Mundo"};
imprimirArray(numeros); // 1 2 3 4 5
imprimirArray(palabras); // Hola Mundo
Los métodos genéricos también pueden tener múltiples parámetros de tipo:
public static <K, V> Map<K, V> crearMapa(K clave, V valor) {
Map<K, V> mapa = new HashMap<>();
mapa.put(clave, valor);
return mapa;
}
Por otro lado, los wildcard types (tipos comodín) sirven para trabajar con generics de manera flexible.
Un wildcard type se representa con el símbolo de interrogación ?
y puede aparecer en tres formas:
- Wildcard sin límites (
?
): Representa "cualquier tipo" - Wildcard con límite superior (
? extends T
): Representa "T o cualquier subtipo de T" - Wildcard con límite inferior (
? super T
): Representa "T o cualquier supertipo de T"
El wildcard sin límites se utiliza cuando el código no depende del tipo específico:
public void procesarLista(List<?> lista) {
for (Object elemento : lista) {
System.out.println(elemento);
}
}
Este método puede recibir una lista de cualquier tipo, pero solo puede acceder a los elementos como Object
.
El wildcard con límite superior permite acceder a los métodos del tipo límite o sus supertipos:
public double sumarNumeros(List<? extends Number> numeros) {
double suma = 0.0;
for (Number numero : numeros) {
suma += numero.doubleValue(); // Acceso seguro a métodos de Number
}
return suma;
}
Este método puede recibir una lista de Number
, Integer
, Double
, etc., y acceder a los métodos definidos en Number
.
Sin embargo, con un wildcard con límite superior no se pueden agregar elementos a la colección (excepto null
), ya que no se puede garantizar el tipo específico:
public void agregarALista(List<? extends Number> numeros) {
// numeros.add(Integer.valueOf(42)); // Error de compilación
numeros.add(null); // Esto sí está permitido
}
El wildcard con límite inferior permite agregar elementos del tipo límite o cualquiera de sus subtipos:
public void agregarEnteros(List<? super Integer> destino) {
destino.add(Integer.valueOf(42)); // Correcto
destino.add(Integer.valueOf(10)); // Correcto
// Integer valor = destino.get(0); // Error de compilación
}
Con este wildcard, se pueden agregar elementos pero no se puede garantizar el tipo exacto al recuperarlos (solo se puede acceder a ellos como Object
).
Estos dos tipos de wildcard se resumen en el principio PECS (Producer Extends, Consumer Super):
- Usar
? extends T
cuando se quiere extraer elementos de una estructura (Producer) - Usar
? super T
cuando se quiere añadir elementos a una estructura (Consumer) - Usar
T
exacto cuando se quiere hacer ambas operaciones
Un ejemplo práctico que ilustra este principio:
public static <T> void copiar(List<? extends T> origen, List<? super T> destino) {
for (T elemento : origen) {
destino.add(elemento);
}
}
Este método permite copiar elementos de una lista origen a una lista destino, donde:
- La lista
origen
actúa como productora de elementos de tipo T - La lista
destino
actúa como consumidora de elementos de tipo T
Los wildcards son útiles al combinar generics con herencia. Por ejemplo, permiten trabajar con estructuras como:
List<Integer> enteros = new ArrayList<>();
List<? extends Number> numeros = enteros; // Correcto
List<Number> numeros = new ArrayList<>();
List<? super Integer> destino = numeros; // Correcto
Pero los wildcards tienen algunas limitaciones:
- No se pueden usar wildcards como parámetros de tipo en instanciaciones de clases
- No se pueden usar wildcards como parámetros de tipo en métodos genéricos
- Se deben utilizar con cuidado para mantener la legibilidad del código
// Incorrecto
Map<?, ?> mapa = new HashMap<?, ?>(); // Error de compilación
// Correcto
Map<?, ?> mapa = new HashMap<>();
Borrado de tipos y limitaciones en tiempo de ejecución
El borrado de tipos (type erasure) es diferente con respecto a lenguajes como C#, que implementan generics a nivel de tiempo de ejecución. Java utiliza elimina la información de tipo genérico durante la compilación para mantener la compatibilidad hacia atrás con versiones anteriores de la JVM.
Cuando el compilador de Java procesa el código con generics, realiza las siguientes transformaciones:
1. Reemplaza todos los parámetros de tipo por su tipo límite o por Object
si no tienen límite
2. Inserta conversiones (casts) automáticas donde sea necesario
3. Genera métodos puente para preservar la semántica de polimorfismo en clases genéricas heredadas
Por ejemplo, el siguiente código genérico:
public class Caja<T> {
private T contenido;
public void guardar(T objeto) {
this.contenido = objeto;
}
public T obtener() {
return contenido;
}
}
Se transforma, aproximadamente, en este código después del borrado de tipos:
public class Caja {
private Object contenido;
public void guardar(Object objeto) {
this.contenido = objeto;
}
public Object obtener() {
return contenido;
}
}
Si la clase se definiera con un límite, como Caja<T extends Number>
, entonces T
se reemplazaría por Number
en lugar de Object
.
Esta técnica de implementación tiene implicaciones y limitaciones:
- La información de tipos genéricos no está disponible en tiempo de ejecución
Una de las consecuencias más importantes del borrado de tipos es que la información sobre los parámetros de tipo no se conserva en tiempo de ejecución. Esto significa que no se puede verificar o acceder a esta información mediante reflection.
List<String> listaStrings = new ArrayList<>();
List<Integer> listaEnteros = new ArrayList<>();
// En tiempo de ejecución, ambas expresiones evalúan a true
System.out.println(listaStrings.getClass() == listaEnteros.getClass()); // true
- No se puede usar el operador
instanceof
con tipos genéricos
Debido al borrado de tipos, no se puede comprobar si un objeto es una instancia de un tipo genérico específico:
public static boolean esListaDeStrings(Object obj) {
// Esto NO compila
return obj instanceof List<String>;
// Solo se puede comprobar si es una List, sin especificar el tipo
return obj instanceof List;
}
- No se pueden crear arrays de tipos genéricos
Java no permite crear arrays de tipos genéricos directamente:
// Esto NO compila
List<String>[] arrayDeListas = new List<String>[10];
// Se debe usar un enfoque sin tipos específicos
List<?>[] arrayDeListas = new List<?>[10];
Esta restricción existe porque los arrays en Java conocen y verifican sus tipos de componentes en tiempo de ejecución, lo que es incompatible con el borrado de tipos.
- No se pueden usar tipos primitivos como parámetros de tipo
Los parámetros de tipo en Java solo pueden ser tipos de referencia, no tipos primitivos:
// Esto NO compila
List<int> listaEnteros = new ArrayList<int>();
// Se deben usar las clases wrapper
List<Integer> listaEnteros = new ArrayList<>();
- No se pueden crear instancias de parámetros de tipo
No se puede utilizar el operador new
con un parámetro de tipo:
public <T> T crearInstancia() {
// Esto NO compila
return new T();
}
Para solucionar esto, se suelen usar técnicas como:
public <T> T crearInstancia(Class<T> clase) throws ReflectiveOperationException {
return clase.getDeclaredConstructor().newInstance();
}
- No se pueden declarar excepciones genéricas
Java no permite crear o capturar excepciones con parámetros de tipo:
// Esto NO compila
class MiExcepcion<T> extends Exception {}
// Esto tampoco compila
public <T extends Exception> void metodo() throws T {}
A pesar de estas limitaciones, hay técnicas y patrones para mitigarlas:
- TypeToken y Super Type Token: Para preservar la información de tipo genérico en tiempo de ejecución, se puede utilizar una técnica conocida como "type token". La biblioteca Guava de Google proporciona una implementación con su clase
TypeToken
.
// Versión simplificada de un TypeToken
public abstract class TypeToken<T> {
private final Type type;
protected TypeToken() {
Type superclass = getClass().getGenericSuperclass();
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
// Uso
TypeToken<List<String>> typeToken = new TypeToken<>() {};
Type type = typeToken.getType(); // Preserva la información List<String>
- Contenedores seguros para tipos: Se pueden diseñar estructuras que mantengan información de tipo en tiempo de ejecución:
public class SafeMap {
private final Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> type, T value) {
map.put(type, value);
}
public <T> T get(Class<T> type) {
return type.cast(map.get(type));
}
}
- Metaprogramación con reflection: Para casos como la creación de instancias de tipos genéricos, se puede utilizar reflection:
public class Factory<T> {
private final Class<T> type;
public Factory(Class<T> type) {
this.type = type;
}
public T create() throws ReflectiveOperationException {
return type.getDeclaredConstructor().newInstance();
}
}
Patrones de diseño con generics: Type Token, Builder genérico, Factory
Type Token
El patrón Type Token permite capturar y preservar información de tipos genéricos en tiempo de ejecución, superando una de las limitaciones del borrado de tipos en Java.
La implementación se basa en que cuando se extiende una clase genérica o se implementa una interfaz genérica con un parámetro de tipo concreto, esa información se conserva en la estructura de la clase como un tipo genérico.
public class TypeToken<T> {
private final Type type;
protected TypeToken() {
// Obtiene la clase actual y su superclase genérica
Type superclass = getClass().getGenericSuperclass();
// Extrae el parámetro de tipo real
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
Para utilizar este TypeToken
, se crea una subclase anónima con el tipo específico:
// Captura el tipo List<String>
TypeToken<List<String>> listStringToken = new TypeToken<>() {};
Type type = listStringToken.getType();
System.out.println(type); // java.util.List<java.lang.String>
// Captura el tipo Map<String, Integer>
TypeToken<Map<String, Integer>> mapToken = new TypeToken<>() {};
Type mapType = mapToken.getType();
System.out.println(mapType); // java.util.Map<java.lang.String, java.lang.Integer>
Este patrón es útil para operaciones como:
- Deserialización JSON/XML tipada
- Registro de convertidores específicos de tipo
- Implementación de inyección de dependencias tipada
Una versión más avanzada, conocida como Super Type Token, puede manejar incluso tipos genéricos anidados más complejos:
public class TypeReference<T> {
private final Type type;
protected TypeReference() {
// Obtiene la clase actual
Class<?> parameterizedTypeClass = getClass();
// Obtiene la superclase genérica
Type genericSuperclass = parameterizedTypeClass.getGenericSuperclass();
// Extrae el parámetro de tipo
this.type = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass().getSuperclass() != obj.getClass().getSuperclass()) return false;
TypeReference<?> other = (TypeReference<?>) obj;
return type.equals(other.type);
}
@Override
public int hashCode() {
return type.hashCode();
}
}
Builder Genérico
El patrón Builder se utiliza para construir objetos complejos paso a paso.
Una implementación genérica del patrón Builder utiliza lo que se conoce como "Curiously Recurring Generic Pattern" (CRGP):
public abstract class GenericBuilder<T, B extends GenericBuilder<T, B>> {
// El objeto que se está construyendo
protected final T objeto;
// Constructor protegido que recibe el objeto a construir
protected GenericBuilder(T objeto) {
this.objeto = objeto;
}
// Método para obtener la instancia del builder concreto
@SuppressWarnings("unchecked")
protected B self() {
return (B) this;
}
// Método para construir el objeto final
public T build() {
return objeto;
}
}
Para usar este builder genérico, se crea una implementación concreta:
public class Persona {
private String nombre;
private int edad;
private String email;
// Constructor package-private para uso del builder
Persona() {}
// Getters
public String getNombre() { return nombre; }
public int getEdad() { return edad; }
public String getEmail() { return email; }
// Builder concreto
public static class Builder extends GenericBuilder<Persona, Builder> {
public Builder() {
super(new Persona());
}
public Builder nombre(String nombre) {
objeto.nombre = nombre;
return self();
}
public Builder edad(int edad) {
objeto.edad = edad;
return self();
}
public Builder email(String email) {
objeto.email = email;
return self();
}
}
}
El uso de este builder es seguro en cuanto a tipos:
Persona persona = new Persona.Builder()
.nombre("Ana")
.edad(30)
.email("ana@ejemplo.com")
.build();
La ventaja de este enfoque genérico es que permite la herencia de builders manteniendo la fluidez de la API:
public class Empleado extends Persona {
private String departamento;
private double salario;
Empleado() {}
public String getDepartamento() { return departamento; }
public double getSalario() { return salario; }
public static class Builder extends Persona.Builder {
public Builder() {
super();
}
public Builder departamento(String departamento) {
((Empleado)objeto).departamento = departamento;
return self();
}
public Builder salario(double salario) {
((Empleado)objeto).salario = salario;
return self();
}
@Override
public Empleado build() {
return (Empleado)objeto;
}
}
}
Factory Genérico
El patrón Factory se utiliza para crear objetos sin especificar la clase exacta del objeto que se creará.
Una implementación simple de una Factory genérica:
public interface Factory<T> {
T create();
}
Esta interfaz se puede implementar para diferentes tipos:
// Factory para crear ArrayList
public class ArrayListFactory<E> implements Factory<ArrayList<E>> {
@Override
public ArrayList<E> create() {
return new ArrayList<>();
}
}
// Factory para crear HashSet
public class HashSetFactory<E> implements Factory<HashSet<E>> {
@Override
public HashSet<E> create() {
return new HashSet<>();
}
}
Un uso más avanzado es un ServiceFactory genérico que crea servicios específicos:
// Interfaz base para servicios
public interface Service {
void ejecutar();
}
// Implementaciones concretas
public class ServicioA implements Service {
@Override
public void ejecutar() {
System.out.println("Ejecutando Servicio A");
}
}
public class ServicioB implements Service {
@Override
public void ejecutar() {
System.out.println("Ejecutando Servicio B");
}
}
// Factory genérica para servicios
public class ServiceFactory<T extends Service> implements Factory<T> {
private final Class<T> tipoServicio;
public ServiceFactory(Class<T> tipoServicio) {
this.tipoServicio = tipoServicio;
}
@Override
public T create() {
try {
return tipoServicio.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException("No se pudo crear el servicio", e);
}
}
}
Uso de estas factorías:
Factory<ArrayList<String>> listFactory = new ArrayListFactory<>();
ArrayList<String> lista = listFactory.create();
Factory<ServicioA> servicioAFactory = new ServiceFactory<>(ServicioA.class);
ServicioA servicioA = servicioAFactory.create();
servicioA.ejecutar(); // "Ejecutando Servicio A"
Otro enfoque es el Abstract Factory genérico:
public interface AbstractFactory<T> {
T crear(String tipo);
}
public class ColeccionFactory<E> implements AbstractFactory<Collection<E>> {
@Override
public Collection<E> crear(String tipo) {
return switch (tipo.toLowerCase()) {
case "list", "arraylist" -> new ArrayList<E>();
case "set", "hashset" -> new HashSet<E>();
case "linkedlist" -> new LinkedList<E>();
case "treeset" -> new TreeSet<E>();
default -> throw new IllegalArgumentException("Tipo desconocido: " + tipo);
};
}
}
Uso:
AbstractFactory<Collection<String>> factory = new ColeccionFactory<>();
Collection<String> lista = factory.crear("list");
Collection<String> conjunto = factory.crear("set");
Otras aplicaciones de generics en patrones de diseño
Repository genérico: Un patrón común en aplicaciones con acceso a datos:
public interface Repository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
T save(T entity);
void deleteById(ID id);
}
public class ProductoRepository implements Repository<Producto, Long> {
// Implementación específica para Producto
}
Decorator genérico: Permite envolver objetos manteniendo su tipo:
public abstract class Decorator<T> implements Component {
protected T componente;
public Decorator(T componente) {
this.componente = componente;
}
}
Singleton con parameter injection:
public class Configuracion<T> {
private static Configuracion<?> instancia;
private final T valor;
private Configuracion(T valor) {
this.valor = valor;
}
@SuppressWarnings("unchecked")
public static <T> Configuracion<T> getInstance(T valor) {
if (instancia == null) {
instancia = new Configuracion<>(valor);
}
return (Configuracion<T>) instancia;
}
public T getValor() {
return valor;
}
}
Ejercicios de esta lección Generics
Evalúa tus conocimientos de esta lección Generics con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Clases abstractas
Listas
Métodos de la clase String
Streams: reduce()
Polimorfismo
Pattern Matching
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Inferencia de tipos con var
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
CRUD en Java de modelo Customer sobre un ArrayList
Tipos de variables
Streams: collect()
Operadores aritméticos
Arrays y matrices
Clases y objetos
Interfaz funcional Consumer
Interfaces
Enumeraciones Enums
API java.nio 2
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Sobrecarga de métodos
Clases sealed
Creación de Streams
Records
Encapsulación
Streams: min max
Métodos avanzados de la clase String
Funciones
Polimorfismo de tiempo de compilación
Reto sintaxis Java
Conjuntos
Estructuras de control
Recursión
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
Operadores
Variables
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
CRUD en Java de modelo Customer sobre un HashMap
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Tipos de datos
Clases abstractas
Instalación
Funciones
Excepciones
Estructuras de control
Herencia de clases
La clase Scanner
Generics
Streams: map()
Funciones y encapsulamiento
Streams: match
Gestión de errores y excepciones
Datos primitivos
Todas las lecciones de Java
Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Java
Introducción Y Entorno
Configuración De Entorno Java
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Recursión
Sintaxis
Excepciones
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Sobrecarga De Métodos
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
La Clase Scanner
Programación Orientada A Objetos
Métodos De La Clase String
Programación Orientada A Objetos
Records
Programación Orientada A Objetos
Pattern Matching
Programación Orientada A Objetos
Inferencia De Tipos Con Var
Programación Orientada A Objetos
Enumeraciones Enums
Programación Orientada A Objetos
Generics
Programación Orientada A Objetos
Clases Sealed
Programación Orientada A Objetos
Listas
Framework Collections
Conjuntos
Framework Collections
Mapas
Framework Collections
Funciones Lambda
Programación Funcional
Interfaz Funcional Consumer
Programación Funcional
Interfaz Funcional Predicate
Programación Funcional
Interfaz Funcional Supplier
Programación Funcional
Interfaz Funcional Function
Programación Funcional
Métodos Referenciados
Programación Funcional
Creación De Streams
Programación Funcional
Operaciones Intermedias Con Streams: Map()
Programación Funcional
Operaciones Intermedias Con Streams: Filter()
Programación Funcional
Operaciones Intermedias Con Streams: Distinct()
Programación Funcional
Operaciones Finales Con Streams: Collect()
Programación Funcional
Operaciones Finales Con Streams: Min Max
Programación Funcional
Operaciones Intermedias Con Streams: Flatmap()
Programación Funcional
Operaciones Intermedias Con Streams: Sorted()
Programación Funcional
Operaciones Finales Con Streams: Reduce()
Programación Funcional
Operaciones Finales Con Streams: Foreach()
Programación Funcional
Operaciones Finales Con Streams: Count()
Programación Funcional
Operaciones Finales Con Streams: Match
Programación Funcional
Api Optional
Programación Funcional
Api Java.nio 2
Entrada Y Salida (Io)
Api Java.time
Api Java.time
Ecosistema Jakarta Ee De Java
Frameworks Para Java
Certificados de superación de Java
Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el concepto de generics y su utilidad en Java.
- Identificar las ventajas de usar generics para mejorar la flexibilidad y seguridad de los tipos.
- Implementar clases e interfaces genéricas en aplicaciones.
- Analizar limitaciones y beneficios del borrado de tipos.
- Aplicar patrones de diseño utilizando generics: Type Token, Builder, Factory.