Menu
Coddy logo textTech

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 sin límites (unbounded wildcard) ? 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>());  // Funciona

El comodín con límite superior ? extends Type acepta el tipo especificado o cualquiera de sus subclases. Utilícelo 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 Double

El comodín de límite inferior (lower bounded wildcard) ? 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 Integer

Una pauta útil: usa extends cuando solo lees (productor), usa super cuando solo escribes (consumidor). Esto se conoce como el principio PECS: Producer Extends, Consumer Super.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema de alimentación para un 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 sin límite (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 base Food que tenga un campo privado name (String), un constructor para inicializarlo y un método getName(). Luego crea dos subclases en el mismo archivo: Meat extiende de Food (el constructor llama a super(name)) y Vegetable extiende de Food (mismo patrón).
  • Animal.java: Crea una clase base Animal con un campo privado species (String), un constructor y un método getSpecies(). Crea dos subclases: Carnivore y Herbivore, 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 sin límite para imprimir cualquier lista. Recorre e imprime cada elemento usando toString(), 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 un int. 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 objeto Meat con el nombre dado y añádelo al stock. Esto demuestra la escritura en un consumidor (consumer).

  • Main.java: ¡Une 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 objeto Meat con el nombre de la carne y un objeto Vegetable con el nombre del vegetal. Imprime Food inventory: y luego llama a printInventory con esta lista.

    A continuación, imprime una línea en blanco, luego imprime Total food items: [count] usando calculateTotalFood en tu lista de comida.

    Finalmente, crea un nuevo ArrayList<Food> llamado meatStock. Imprime una línea en blanco, luego Adding to meat stock.... Llama a addMeatToStock con esta lista y la cadena "Beef". Luego imprime Meat stock after adding: y llama a printInventory con 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.

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
    }
}
quiz iconPonte a prueba

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