Comodines (?, extends, super)
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 57 de 87.
Los parámetros de tipo acotados restringen los genéricos a tipos específicos, pero ¿qué pasa cuando estás trabajando con tipos genéricos existentes y no conoces —o no te importa— el parámetro de tipo exacto? Aquí es donde entran en juego los comodines (wildcards).
El comodín no acotado ? representa un tipo desconocido. Es útil cuando quieres aceptar cualquier tipo parametrizado:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // Funciona
printList(new ArrayList<Integer>()); // FuncionaEl comodín con límite superior ? extends Type acepta el tipo especificado o cualquiera de sus subclases. Úselo cuando necesite leer de una estructura genérica:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // Lista de Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // Lista de DoubleEl comodín de límite inferior ? super Type acepta el tipo especificado o cualquiera de sus superclases. Úselo cuando necesite escribir en una estructura genérica:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // Funciona - Number es un supertipo de IntegerUna pauta útil: utiliza extends cuando solo leas (productor), utiliza super cuando solo escribas (consumidor). Esto se conoce como el principio PECS: Productor Extends, Consumidor Super.
Desafío
Fácil¡Vamos a construir un sistema de alimentación de zoológico que demuestre los tres tipos de comodines (wildcards)! Crearás una jerarquía de animales y comida, luego escribirás métodos de utilidad que utilicen comodines ilimitados (unbounded), con límite superior (upper bounded) y con límite inferior (lower bounded) para manejar diferentes escenarios de alimentación.
Organizarás tu código en cuatro archivos:
Food.java: Crea una jerarquía de clases simple para la comida. Comienza con una clase baseFoodque tenga un campo privadoname(String), un constructor para inicializarlo y un métodogetName(). Luego crea dos subclases en el mismo archivo:Meatque extiende de Food (el constructor llama asuper(name)) yVegetableque extiende de Food (mismo patrón).Animal.java: Crea una clase baseAnimalcon un campo privadospecies(String), un constructor y un métodogetSpecies(). Crea dos subclases:CarnivoreyHerbivore, cada una extendiendo de Animal y llamando al constructor del padre.ZooFeeder.java: ¡Aquí es donde brillan los comodines! Crea una clase de utilidad con tres métodos estáticos:printInventory(List<?> items)- Utiliza un comodín ilimitado para imprimir cualquier lista. Recorre e imprime cada elemento usandotoString(), uno por línea.calculateTotalFood(List<? extends Food> foods)- Utiliza un comodín con límite superior para leer de una lista de cualquier tipo de Food. Devuelve el recuento de elementos como unint. Esto demuestra la lectura desde un productor (producer).addMeatToStock(List<? super Meat> stock, String meatName)- Utiliza un comodín con límite inferior para escribir en una lista que pueda contener Meat. Crea un nuevo objetoMeatcon el nombre dado y añádelo al stock. Esto demuestra la escritura en un consumidor (consumer).Main.java: ¡Une todo tu sistema de alimentación del zoológico! Recibirás dos entradas: un nombre de carne (String) y un nombre de vegetal (String).Primero, crea un
ArrayList<Food>y añade un objetoMeatcon el nombre de la carne y un objetoVegetablecon el nombre del vegetal. Imprime"Food inventory:"y luego llama aprintInventorycon esta lista.A continuación, imprime una línea en blanco, luego imprime
"Total food items: "seguido del conteo usandocalculateTotalFooden tu lista de comida.Finalmente, crea un nuevo
ArrayList<Food>llamadomeatStock. Imprime una línea en blanco, luego"Adding to meat stock...". Llama aaddMeatToStockcon esta lista y el string"Beef". Luego imprime"Meat stock after adding:"y llama aprintInventorysobre el stock de carne.
Recibirás dos entradas en orden: un nombre de carne y un nombre de vegetal.
Sobrescribe toString() en tu clase Food para que devuelva el nombre de la comida, de modo que la impresión funcione correctamente. ¡Observa cómo cada tipo de comodín sirve para un propósito diferente: ? para la máxima flexibilidad cuando solo necesitas leer como Object, ? extends al leer tipos específicos y ? super al escribir en una colección!
Hoja de referencia
Java proporciona tres tipos de comodines (wildcards) para trabajar con tipos genéricos cuando el parámetro de tipo exacto es desconocido o irrelevante.
Comodín sin restricciones (?)
Representa un tipo desconocido y acepta cualquier tipo parametrizado:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // Works
printList(new ArrayList<Integer>()); // WorksComodín con límite superior (? extends Type)
Acepta el tipo especificado o cualquiera de sus subclases. Úselo cuando necesite leer de una estructura genérica:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // List of Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // List of DoubleComodín con límite inferior (? super Type)
Acepta el tipo especificado o cualquiera de sus superclases. Úselo cuando necesite escribir en una estructura genérica:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // Works - Number is a supertype of IntegerPrincipio PECS
Producer Extends, Consumer Super: Use extends cuando solo lea (productor), use super cuando solo escriba (consumidor).
Pruébalo tú mismo
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: Crear un ArrayList<Food> y añadir objetos Meat y Vegetable
// TODO: Imprimir "Food inventory:" y luego llamar a printInventory
// TODO: Imprimir una línea en blanco, luego "Total food items: [count]" usando calculateTotalFood
// TODO: Crear un nuevo ArrayList<Food> llamado meatStock
// TODO: Imprimir una línea en blanco, luego "Adding to meat stock..."
// TODO: Llamar a addMeatToStock con meatStock y "Beef"
// TODO: Imprimir "Meat stock after adding:" y luego llamar a printInventory sobre meatStock
}
}
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