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 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. Ú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 Double

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

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

challenge icon

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 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 que extiende de Food (el constructor llama a super(name)) y Vegetable que 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 ilimitado 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 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 luego llama a printInventory con esta lista.

    A continuación, imprime una línea en blanco, luego imprime "Total food items: " seguido del conteo 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 el string "Beef". Luego imprime "Meat stock after adding:" y llama a printInventory sobre 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>());  // Works

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));        // List of Integer
sumNumbers(Arrays.asList(1.5, 2.5));       // List of Double

Comodí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 Integer

Principio 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
    }
}
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