Menu
Coddy logo textTech

Wildcards (?, extends, super)

Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 57 de 87.

Parâmetros de tipo delimitados restringem genéricos a tipos específicos, mas e quando você está trabalhando com tipos genéricos existentes e não sabe — ou não se importa com — o parâmetro de tipo exato? É aqui que entram os wildcards.

O wildcard ilimitado ? representa um tipo desconhecido. É útil quando você deseja aceitar qualquer 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

O wildcard de limite superior ? extends Type aceita o tipo especificado ou qualquer uma de suas subclasses. Use-o quando precisar ler de uma estrutura 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

O wildcard de limite inferior (lower bounded wildcard) ? super Type aceita o tipo especificado ou qualquer uma de suas superclasses. Use-o quando precisar escrever em uma estrutura 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 é um supertipo de Integer

Uma diretriz útil: use extends quando você apenas lê (produtor), use super quando você apenas escreve (consumidor). Isso é conhecido como o princípio PECS — Producer Extends, Consumer Super.

challenge icon

Desafio

Fácil

Vamos construir um sistema de alimentação de zoológico que demonstra todos os três tipos de wildcards! Você criará uma hierarquia de animais e alimentos e, em seguida, escreverá métodos utilitários que usam wildcards ilimitados (unbounded), com limite superior (upper bounded) e com limite inferior (lower bounded) para lidar com diferentes cenários de alimentação.

Você organizará seu código em quatro arquivos:

  • Food.java: Crie uma hierarquia de classes simples para alimentos. Comece com uma classe base Food que possui um campo privado name (String), um construtor para inicializá-lo e um método getName(). Em seguida, crie duas subclasses no mesmo arquivo: Meat estende Food (o construtor chama super(name)) e Vegetable estende Food (mesmo padrão).
  • Animal.java: Crie uma classe base Animal com um campo privado species (String), um construtor e um método getSpecies(). Crie duas subclasses: Carnivore e Herbivore, cada uma estendendo Animal e chamando o construtor pai.
  • ZooFeeder.java: É aqui que os wildcards brilham! Crie uma classe utilitária com três métodos estáticos:

    printInventory(List<?> items) - Usa um wildcard ilimitado para imprimir qualquer lista. Percorra e imprima cada item usando toString(), um por linha.

    calculateTotalFood(List<? extends Food> foods) - Usa um wildcard com limite superior para ler de uma lista de qualquer tipo de Food. Retorne a contagem de itens como um int. Isso demonstra a leitura de um produtor.

    addMeatToStock(List<? super Meat> stock, String meatName) - Usa um wildcard com limite inferior para escrever em uma lista que pode conter Meat. Crie um novo objeto Meat com o nome fornecido e adicione-o ao estoque. Isso demonstra a escrita em um consumidor.

  • Main.java: Reúna seu sistema de alimentação do zoológico! Você receberá duas entradas: um nome de carne (String) e um nome de vegetal (String).

    Primeiro, crie um ArrayList<Food> e adicione um objeto Meat com o nome da carne e um objeto Vegetable com o nome do vegetal. Imprima Food inventory: e chame printInventory com esta lista.

    Em seguida, imprima uma linha em branco e, depois, imprima Total food items: [count] usando calculateTotalFood em sua lista de alimentos.

    Finalmente, crie um novo ArrayList<Food> chamado meatStock. Imprima uma linha em branco e, depois, Adding to meat stock.... Chame addMeatToStock com esta lista e a string "Beef". Em seguida, imprima Meat stock after adding: e chame printInventory no estoque de carne.

Você receberá duas entradas em ordem: um nome de carne e um nome de vegetal.

Sobrescreva toString() em sua classe Food para retornar o nome do alimento, para que a impressão funcione corretamente. Observe como cada tipo de wildcard serve a um propósito diferente: ? para flexibilidade máxima quando você só precisa ler como Object, ? extends ao ler tipos específicos e ? super ao escrever em uma coleção!

Folha de consulta

Java fornece três tipos de wildcards (curingas) para trabalhar com tipos genéricos quando o parâmetro de tipo exato é desconhecido ou irrelevante.

Wildcard Ilimitado (?)

Representa um tipo desconhecido e aceita qualquer 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

Wildcard com Limite Superior (? extends Type)

Aceita o tipo especificado ou qualquer uma de suas subclasses. Use quando precisar ler de uma estrutura 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

Wildcard com Limite Inferior (? super Type)

Aceita o tipo especificado ou qualquer uma de suas superclasses. Use quando precisar escrever em uma estrutura 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 é um supertipo de Integer

Princípio PECS

Producer Extends, Consumer Super: Use extends quando você apenas lê (produtor), use super quando você apenas escreve (consumidor).

Experimente você mesmo

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: Crie um ArrayList<Food> e adicione objetos Meat e Vegetable
        
        // TODO: Imprima "Food inventory:" e depois chame printInventory
        
        // TODO: Imprima uma linha em branco, depois "Total food items: [count]" usando calculateTotalFood
        
        // TODO: Crie um novo ArrayList<Food> chamado meatStock
        
        // TODO: Imprima uma linha em branco, depois "Adding to meat stock..."
        
        // TODO: Chame addMeatToStock com meatStock e "Beef"
        
        // TODO: Imprima "Meat stock after adding:" e depois chame printInventory em meatStock
    }
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos