W3docs

Wildcards Genéricos em Java

Use wildcards com limite superior, inferior e ilimitados em Java generics, e a regra PECS.

Um wildcard é o token ? que aparece em tipos genéricos no lugar de um argumento de tipo concreto — List<?>, List<? extends Number>, List<? super Integer>. É a resposta para um problema que você encontra quase imediatamente ao começar a escrever código genérico: List<Integer> não é um subtipo de List<Number>, mesmo que Integer seja subtipo de Number. Wildcards são a forma de descrever "uma lista de algum Number" sem se comprometer com um tipo de elemento específico — e são, de longe, a parte mais confusa do sistema de tipos do Java.

O ponto de partida contraintuitivo

Aqui está o fato que torna os wildcards necessários:

List<Integer> ints  = List.of(1, 2, 3);
List<Number>  nums  = ints;            // ❌ does not compile

Mesmo que Integer extends Number, List<Integer> não estende List<Number>. Tipos genéricos são invariantesList<Sub> e List<Super> são não relacionados, independentemente do que Sub e Super sejam.

O motivo é sólido, embora surpreendente. Se List<Integer> fosse uma List<Number>, você poderia fazer isso:

List<Number> nums = ints;     // pretend this is legal
nums.add(3.14);               // legal — 3.14 is a Number
int x = ints.get(3);          // KABOOM at runtime — it's a Double

O cast na última linha explodiria. Para evitar que isso aconteça, o compilador recusa logo o primeiro passo: List<Integer> não é uma List<Number>. Ponto final.

Wildcards são a forma de recuperar a flexibilidade com segurança.

O wildcard ilimitado: List<?>

O wildcard mais simples é o solitário ? — "uma lista de algum tipo desconhecido":

public static void printAll(List<?> list) {
  for (Object o : list) System.out.println(o);
}

printAll(List.of(1, 2, 3));          // List<Integer> — OK
printAll(List.of("a", "b"));         // List<String>  — OK
printAll(new ArrayList<>());         // List<Object>  — OK

Dentro do corpo, a única coisa que você pode fazer com os elementos de uma List<?> é lê-los como Object — porque o compilador não tem ideia do que ? é. Você não pode adicionar nada a uma List<?> (com a única exceção de null):

public static void corrupt(List<?> list) {
  list.add("hello");   // ❌ does not compile — ? is unknown
  list.add(null);      // ✓ — null is a value of every reference type
}

List<?> é o que você escreve quando quer expressar "aceito qualquer lista e vou apenas ler dela como Object."

Wildcard com limite superior: ? extends T

Quando você precisa ler os elementos como um tipo específico — digamos, tratá-los todos como Number — use um wildcard com limite superior:

public static double sum(List<? extends Number> list) {
  double total = 0;
  for (Number n : list) total += n.doubleValue();   // legal — every element IS-A Number
  return total;
}

sum(List.of(1, 2, 3));          // List<Integer> — OK, Integer extends Number
sum(List.of(1.5, 2.5));         // List<Double>  — OK
sum(List.of(1L, 2L, 3L));       // List<Long>    — OK

List<? extends Number> se lê como "uma lista de algum tipo específico que é Number ou um subtipo de Number." Você pode ler dela como Number. Você não pode adicionar nada a ela, novamente com a exceção de null — porque o compilador não sabe qual subtipo de Number a lista realmente contém. Adicionar um Integer a uma List<? extends Number> que é secretamente uma List<Double> a corromperia; em vez de tentar descobrir qual subtipo é, o compilador simplesmente recusa todo add.

Wildcard com limite inferior: ? super T

A imagem espelhada. ? super T significa "o tipo de elemento da lista é T ou algum supertipo de T":

public static void addOneTwoThree(List<? super Integer> list) {
  list.add(1);
  list.add(2);
  list.add(3);
}

List<Integer> ints   = new ArrayList<>();   addOneTwoThree(ints);   // ✓
List<Number>  nums   = new ArrayList<>();   addOneTwoThree(nums);   // ✓ — Number is a supertype of Integer
List<Object>  objs   = new ArrayList<>();   addOneTwoThree(objs);   // ✓ — Object is too

Aqui você pode adicionar qualquer Integer (ou subtipo) com segurança — o tipo de elemento da lista é garantidamente Integer ou algum ancestral dele, então um Integer cabe. O que você não pode fazer é ler um tipo específico — o máximo que pode dizer sobre um elemento é que é um Object, porque a lista real pode ser List<Object>.

A regra PECS

Existe um mnemônico que todo desenvolvedor Java eventualmente memoriza:

PECS — Producer Extends, Consumer Super.

É a regra prática para saber qual wildcard usar:

  • Se o parâmetro produz valores (você lê dele): use ? extends T.
  • Se o parâmetro consome valores (você escreve nele): use ? super T.

A assinatura canônica que ela produz é Collections.copy:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
  for (int i = 0; i < src.size(); i++) {
    dest.set(i, src.get(i));
  }
}

src é lido (ele produz Ts) — ? extends T. dest é escrito (ele consome Ts) — ? super T. Essa é toda a razão da assimetria: a mesma assinatura funciona se src for uma List<Integer> e dest for uma List<Number>, ou o contrário, desde que T seja um ponto de encontro entre eles.

Se você não se lembrar de mais nada deste capítulo, lembre-se de PECS.

Quando não usar um wildcard

Se um parâmetro é tanto lido quanto escrito no mesmo método, nem ? extends T nem ? super T funciona — nenhum permite fazer os dois. Nesse caso, use simplesmente um parâmetro de tipo normal:

public static <T> void swap(List<T> list, int i, int j) {
  T tmp = list.get(i);              // read
  list.set(i, list.get(j));         // write
  list.set(j, tmp);                 // write
}

Um wildcard é a ferramenta certa quando um lado da relação é "só lejo" ou "só escrevo." Um parâmetro de tipo é a ferramenta certa quando você precisa se referir a um tipo de elemento específico nos dois lados.

Wildcards vs. parâmetros de tipo com limite

Compare:

public static <T extends Number> double sumNamed(List<T> list)         { ... }
public static            double sumWildcard(List<? extends Number> list) { ... }

Funcionalmente, ambos aceitam o mesmo conjunto de argumentos. A diferença é o que o corpo pode expressar:

  • A forma nomeada (<T extends Number>) fornece um nome T — útil se você quiser retornar T, aceitar outra List<T> como segundo parâmetro, ou escrever T tmp = list.get(0) para preservar o tipo de elemento exato.
  • A forma com wildcard (? extends Number) não fornece nome — você só pode se referir aos elementos como Number. É mais restrita na API (nenhum nome vaza para a assinatura), mas menos expressiva no corpo.

Regra prática: se você só precisa dos elementos como Number, o wildcard é a escolha menor e mais limpa. Se o corpo precisa se referir a um T específico, nomeie-o.

Um exemplo trabalhado: PECS na prática

O programa copia elementos de uma lista para outra e calcula a soma acumulada — ambas as operações parametrizadas da forma PECS. Observe os call sites: copyOf(intList, numberList) mistura tipos de elementos porque os wildcards permitem que um destino Number aceite valores Integer.

java— editable, runs on the server

sum aceita uma List<Integer> e uma List<Double> porque o wildcard diz "algum subtipo de Number." fillWithSquares adiciona valores Integer em uma List<Number> porque o wildcard diz "qualquer lista que possa conter Integer ou um de seus ancestrais." copyTo usa ambos — a origem é um produtor, o destino é um consumidor, e T é o tipo de elemento compartilhado que o compilador infere a partir do acordo entre os dois lados.

O que vem a seguir

Você viu as quatro formas como os generics aparecem no código-fonte — classes, métodos, interfaces e wildcards. Agora olhamos uma camada abaixo para ver como a JVM realmente implementa tudo isso. A resposta — type erasure — explica algumas restrições surpreendentes (sem new T(), sem instanceof T, sem arrays genéricos) e é o único insight que faz a história dos generics em Java fazer sentido. Continue para Java Type Erasure.

Prática

Prática
Você está escrevendo `addAll(Collection<? extends T> src, Collection<? super T> dest)`. Por que exatamente essa combinação de wildcards?
Você está escrevendo `addAll(Collection<? extends T> src, Collection<? super T> dest)`. Por que exatamente essa combinação de wildcards?
Was this page helpful?