W3docs

Parâmetros de Tipo Limitados em Java

Restrinja tipos genéricos Java com parâmetros limitados usando extends — limites simples e múltiplos explicados com exemplos.

Um parâmetro de tipo limitado é um parâmetro de tipo com uma cláusula extends que restringe quais tipos podem preenchê-lo. Sem um limite, T é tratado como Object — o compilador não tem como saber se T possui um compareTo, um intValue ou qualquer outro método. Com um limite, você faz uma promessa ao compilador sobre o que T é, e em troca o compilador permite que você chame os métodos que essa promessa implica. Quase todo método genérico interessante tem pelo menos um limite em algum lugar de sua assinatura.

A forma básica: <T extends Bound>

A sintaxe usa extends entre o parâmetro e seu limite — a mesma palavra-chave seja o limite uma classe ou uma interface:

public static <T extends Number> double sum(List<T> values) {
  double total = 0;
  for (T n : values) total += n.doubleValue();   // legal — T is-a Number
  return total;
}

Leia <T extends Number> como "qualquer T que seja um Number ou uma subclasse dele." Dentro do corpo, você pode tratar qualquer valor T como um Number — chamar doubleValue(), intValue(), qualquer coisa na API pública de Number.

Use da mesma forma que qualquer outro método genérico:

sum(List.of(1, 2, 3));            // T = Integer — fine
sum(List.of(1.5, 2.5));           // T = Double  — fine
sum(List.of(1L, 2L, 3L));         // T = Long    — fine
sum(List.of("a", "b"));           // ❌ String is not a Number

Sem o limite, sum não poderia nem tentar chamar doubleValue()T seria apagado para Object, e Object não possui esse método. O limite é o que torna o corpo legal.

extends funciona para interfaces também

Em generics Java, extends é sobrecarregado — significa "é um subtipo de", seja o limite uma classe ou uma interface. Não existe palavra-chave implements separada em uma lista de parâmetros de tipo:

public static <T extends Comparable<T>> T max(List<T> list) {
  T best = list.get(0);
  for (T candidate : list) {
    if (candidate.compareTo(best) > 0) best = candidate;
  }
  return best;
}

max(List.of(3, 1, 4, 1, 5, 9));            // T = Integer
max(List.of("Ada", "Grace", "Linus"));     // T = String

Comparable<T> é uma interface, mas a sintaxe ainda usa extends. Leia <T extends Comparable<T>> como "qualquer T que seja comparável a si mesmo" — a restrição é o que permite chamar candidate.compareTo(best) dentro do loop.

O pequeno detalhe aqui é o <T> dentro do limite — é a forma autorreferencial que vimos em interfaces genéricas. Isso garante que compareTo aceite um T, não apenas qualquer Comparable. Sem isso, você poderia comparar Integer com String e o sistema de tipos não perceberia.

Limites múltiplos

Um parâmetro de tipo pode ter mais de um limite, unidos por &:

public static <T extends Number & Comparable<T>> T maxNumber(List<T> list) {
  T best = list.get(0);
  for (T n : list) {
    if (n.compareTo(best) > 0) best = n;
  }
  return best;
}

Agora T deve ser tanto um Number quanto um Comparable<T>. Dentro do corpo, você pode chamar qualquer método de ambos. Integer, Long, Double, BigDecimal satisfazem ambos — você pode passar qualquer um deles.

Algumas regras:

  • O limite de classe (se houver) deve vir primeiro. <T extends Number & Comparable<T>> é legal; <T extends Comparable<T> & Number> não é.
  • No máximo um limite de classe. Java tem herança simples para classes, então isso decorre das regras existentes da linguagem.
  • Qualquer quantidade de limites de interface. Empilhe quantos forem genuinamente necessários; na prática, dois é o máximo habitual antes que o design precise ser repensado.

A regra de classe primeiro reflete o bytecode: o apagamento substitui T pelo seu limite mais à esquerda, então a posição mais à esquerda é especial. Voltaremos a isso em Apagamento de Tipo.

Limites em parâmetros de tipo no nível de classe

Limites não são exclusivos de métodos. Uma classe ou interface genérica pode limitar seus parâmetros de tipo na declaração:

public class SortedBag<T extends Comparable<T>> {
  private final List<T> items = new ArrayList<>();

  public void add(T item) {
    items.add(item);
    Collections.sort(items);
  }

  public T smallest() { return items.get(0); }
}

SortedBag<Integer> bag = new SortedBag<>();   // OK — Integer is Comparable<Integer>
// SortedBag<Object> bag2 = new SortedBag<>(); // ❌ Object is not Comparable<Object>

Este é o lugar certo para um limite que se aplica à classe inteira. Todo método, todo campo, toda operação padrão tem acesso à restrição.

Limites inferiores pertencem a wildcards, não a parâmetros de tipo

Uma confusão comum: parâmetros de tipo podem ter limites superiores (extends) mas não limites inferiores (super). Isso é legal:

public static <T extends Number> void foo(T x) { ... }

Isso não é:

public static <T super Integer> void bar(T x) { ... }   // ❌ no such syntax

Limites inferiores existem nos generics Java — mas vivem apenas em wildcards, o token ?, não em parâmetros de tipo nomeados. Abordaremos a história completa de ? extends / ? super no capítulo de Wildcards; por ora, saiba apenas que super funciona em ? e não em T.

Quando adicionar um limite

O padrão deve ser "sem limite" — quanto mais solta a restrição, mais tipos seu método aceita. Adicione um quando, e somente quando, o corpo precisar chamar um método específico em T:

  • "Preciso comparar valores de T" → <T extends Comparable<T>>.
  • "Preciso somar valores de T como números" → <T extends Number>.
  • "Preciso ler campos de T como um User" → <T extends User>.
  • "Preciso que T seja auto-fechável para usar em try-with-resources" → <T extends AutoCloseable>.

Se você não está chamando um método em T, não precisa de um limite — e adicionar um mesmo assim apenas restringe sua API sem razão.

Um exemplo completo: um between limitado e um top ordenado

Dois métodos, cada um com um estilo diferente de limite. between usa Comparable<T> para poder fixar o valor; topN usa Number & Comparable<T> para poder tanto comparar quanto reportar uma soma numérica.

java— editable, runs on the server

between aceita qualquer Comparable — inclusive Character, que é Comparable<Character> e não tem nada a ver com números. topN é mais restrito — precisa tanto de ordenação (para ordenar) quanto de valores numéricos (para somar), então empilha os dois limites com &. Cada método pede exatamente a capacidade que usa, nem mais, nem menos.

O que vem a seguir

Um limite em um parâmetro de tipo fixa um único tipo no ponto de chamada — List<Integer> significa "esta lista, este método, este tipo." Às vezes você quer ser menos específico: "uma lista de algum Number — pode ser Integer, pode ser Double, tanto faz." É para isso que servem os wildcards, e eles resolvem uma das maiores fontes de confusão no sistema de tipos Java. Continue para Wildcards Genéricos Java.

Prática

Prática
Você precisa de um método que encontre o maior elemento de uma lista. Qual assinatura permite chamar `compareTo` nos elementos sem restringir os chamadores mais do que o necessário?
Você precisa de um método que encontre o maior elemento de uma lista. Qual assinatura permite chamar `compareTo` nos elementos sem restringir os chamadores mais do que o necessário?
Was this page helpful?