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.

Os parâmetros de tipo limitados restringem os tipos 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 curingas.

O curinga não limitado ? representa um tipo desconhecido. Ele é ú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 curinga com 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 curinga com limite inferior ? 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 orientação útil: use extends quando você apenas lê (produtor) e use super quando você apenas escreve (consumidor). Isso é conhecido como o princípio PECS: Producer Extends, Consumer Super.

challenge icon

Desafio

Fácil

Vamos criar um sistema de alimentação de zoológico que demonstra os três tipos de curingas! Você criará uma hierarquia de animais e alimentos e, em seguida, escreverá métodos utilitários que usam curingas sem limite, com limite superior e com limite inferior 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 tenha 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 (seguindo o 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 da classe pai.
  • ZooFeeder.java: É aqui que os curingas mostram seu poder! Crie uma classe utilitária com três métodos estáticos:

    printInventory(List<?> items) — Usa um curinga sem limite para imprimir qualquer lista. Percorra a lista e imprima cada item usando toString(), um por linha.

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

    addMeatToStock(List<? super Meat> stock, String meatName) — Usa um curinga com limite inferior para escrever em uma lista que pode armazenar 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, em seguida, chame printInventory com essa lista.

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

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

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

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

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: Print "Food inventory:" then call printInventory
        
        // TODO: Print blank line, then "Total food items: [count]" using calculateTotalFood
        
        // TODO: Crie um novo ArrayList<Food> chamado meatStock
        
        // TODO: Print blank line, then "Adding to meat stock..."
        
        // TODO: Chame addMeatToStock com meatStock e "Beef"
        
        // TODO: Print "Meat stock after adding:" then call printInventory on 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

Pratique por conta própria: Compilador de Java online