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é ocurre cuando trabajas 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.
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. Úsalo cuando necesites 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 con límite inferior ? super Type acepta el tipo especificado o cualquiera de sus superclases. Úsalo cuando necesites 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: usa extends cuando solo leas (productor); usa super cuando solo escribas (consumidor). Esto se conoce como el principio PECS: Producer Extends, Consumer Super.
Desafío
Fácil¡Construyamos un sistema de alimentación para un zoológico que demuestre los tres tipos de comodines! Crearás una jerarquía de animales y alimentos, y después escribirás métodos utilitarios que usen comodines no acotados, acotados superiormente y acotados inferiormente para gestionar diferentes escenarios de alimentación.
Organizarás tu código en cuatro archivos:
Food.java: Crea una jerarquía de clases sencilla para los alimentos. Empieza con una clase baseFoodque tenga un campo privadoname(String), un constructor para inicializarlo y un métodogetName(). Después, crea dos subclases en el mismo archivo:Meatextends Food (el constructor llama asuper(name)) yVegetableextends Food (siguiendo el 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 Animal y llamando al constructor de la clase padre.ZooFeeder.java: ¡Aquí es donde los comodines demuestran su utilidad! Crea una clase utilitaria con tres métodos estáticos:printInventory(List<?> items)- Usa un comodín no acotado para imprimir cualquier lista. Recorre la lista e imprime cada elemento usandotoString(), uno por línea.calculateTotalFood(List<? extends Food> foods)- Usa un comodín acotado superiormente para leer de una lista de cualquier tipo de Food. Devuelve la cantidad de elementos como unint. Esto demuestra la lectura desde un productor.addMeatToStock(List<? super Meat> stock, String meatName)- Usa un comodín acotado inferiormente para escribir en una lista que pueda contener Meat. Crea un nuevo objetoMeatcon el nombre indicado y añádelo al inventario. Esto demuestra la escritura en un consumidor.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. ImprimeFood inventory:y después llama aprintInventorycon esta lista.A continuación, imprime una línea en blanco y después imprime
Total food items: [count]usandocalculateTotalFooden tu lista de alimentos.Por último, crea un nuevo
ArrayList<Food>llamadomeatStock. Imprime una línea en blanco y despuésAdding to meat stock.... Llama aaddMeatToStockcon esta lista y la cadena"Beef". Después, imprimeMeat stock after adding:y llama aprintInventoryen el inventario de carne.
Recibirás dos entradas en este orden: un nombre de carne y un nombre de vegetal.
Sobrescribe toString() en tu clase Food para que devuelva el nombre del alimento, de modo que la impresión funcione correctamente. Observa cómo cada tipo de comodín cumple una función diferente: ? para obtener la máxima flexibilidad cuando solo necesitas leer como Object, ? extends cuando lees tipos específicos y ? super cuando escribes en una colección.
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: Crea un ArrayList<Food> y añade objetos Meat y Vegetable
// TODO: Print "Food inventory:" then call printInventory
// TODO: Print blank line, then "Total food items: [count]" using calculateTotalFood
// TODO: Crea un nuevo ArrayList<Food> llamado meatStock
// TODO: Print blank line, then "Adding to meat stock..."
// TODO: Llama a addMeatToStock con meatStock y "Beef"
// TODO: Print "Meat stock after adding:" then call printInventory on 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 IteratorPractica por tu cuenta: Compilador de Java online