Desafio Java

Wildcards e cópia de listas

Enunciado

Implemente um método genérico copiar que copia todos os elementos de uma lista origem para uma lista destino. A assinatura deve ser public static <T> void copiar(List<? extends T> origem, List<? super T> destino). O método deve adicionar cada elemento da origem ao destino. No main, crie uma List<Integer> origem e uma List<Number> destino, copie e imprima o destino. Explique por que os wildcards são necessários.

Requisitos

  • A assinatura do método deve ser exatamente public static <T> void copiar(List<? extends T> origem, List<? super T> destino).
  • O método deve percorrer a origem e adicionar cada elemento ao destino.
  • O main deve demonstrar a cópia de List<Integer> para List<Number> e imprimir o resultado.

Código inicial

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // TODO: criar listas e chamar copiar
    }

    // TODO: implementar copiar
}

Saída esperada

Destino: [10, 20, 30]
Ver dica

Use ? extends T para a origem (produtor) e ? super T para o destino (consumidor). Lembre-se do princípio PECS.

Mostrar solução
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> origem = List.of(10, 20, 30);
        List<Number> destino = new ArrayList<>();
        copiar(origem, destino);
        System.out.println("Destino: " + destino);
    }

    public static <T> void copiar(List<? extends T> origem, List<? super T> destino) {
        for (T item : origem) {
            destino.add(item);
        }
    }
}

Passo a passo

  1. O método copiar é genérico e usa wildcards: ? extends T na origem e ? super T no destino.
  2. O wildcard ? extends T permite que a origem seja uma lista de qualquer subtipo de T, então podemos ler elementos como T.
  3. O wildcard ? super T permite que o destino seja uma lista de qualquer supertipo de T, então podemos adicionar elementos do tipo T.
  4. No loop, cada elemento da origem é lido como T e adicionado ao destino.
  5. No main, criamos List<Integer> e List<Number>. A inferência de tipo define T como Integer, e a chamada é válida porque Integer é subtipo de Number.
  6. O destino é uma ArrayList<Number> que aceita Integer.
  7. Imprimimos o destino, que contém os elementos copiados.

Por que funciona

Os wildcards seguem o princípio PECS (Producer Extends, Consumer Super): a origem é um produtor de elementos (usamos extends), e o destino é um consumidor (usamos super). Isso permite que o método seja mais flexível do que usar apenas List<T> para ambos, pois aceita listas de subtipos e supertipos. A type erasure garante que em runtime não há sobrecarga, mas o compilador verifica a segurança.

Erros comuns

  • Usar List<T> para ambos os parâmetros: isso restringiria a chamada a listas do mesmo tipo exato, impedindo List<Integer> para List<Number>.
  • Inverter os wildcards: List<? super T> origem e List<? extends T> destino causaria erro ao adicionar, pois não se pode adicionar a ? extends T.
  • Tentar adicionar à origem: origem.add(...) não compila porque ? extends T é somente leitura.
  • Esquecer de importar ArrayList e List.

Outra forma de resolver

Usar Collections.copy(destino, origem), mas ela exige que o destino tenha tamanho suficiente e não lida com wildcards da mesma forma. O método manual é mais didático.

Saída esperada

Destino: [10, 20, 30]