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é 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>());  // Funciona

El 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 Double

El 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 Integer

Una 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.

challenge icon

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 base Food que tenga un campo privado name (String), un constructor para inicializarlo y un método getName(). Después, crea dos subclases en el mismo archivo: Meat extends Food (el constructor llama a super(name)) y Vegetable extends Food (siguiendo el 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 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 usando toString(), 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 un int. 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 objeto Meat con 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 objeto Meat con el nombre de la carne y un objeto Vegetable con el nombre del vegetal. Imprime Food inventory: y después llama a printInventory con esta lista.

    A continuación, imprime una línea en blanco y después imprime Total food items: [count] usando calculateTotalFood en tu lista de alimentos.

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

Practica por tu cuenta: Compilador de Java online