W3docs

Parámetros de tipo acotados en Java

Aprende a restringir tipos genéricos en Java con parámetros acotados usando extends — con límites simples y múltiples.

Un parámetro de tipo acotado es un parámetro de tipo con una cláusula extends que restringe qué tipos pueden ocupar su lugar. Sin un límite, T se trata como Object — el compilador no puede saber si T tiene un compareTo, un intValue u otro método. Con un límite, haces una promesa al compilador sobre lo que T es, y a cambio el compilador te permite llamar a los métodos que esa promesa implica. Casi todo método genérico interesante tiene al menos un límite en algún lugar de su firma.

La forma básica: <T extends Bound>

La sintaxis es extends entre el parámetro y su límite — la misma palabra clave tanto si el límite es una clase como una interfaz:

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;
}

Lee <T extends Number> como "cualquier T que sea un Number o una subclase de él". Dentro del cuerpo, puedes tratar cualquier valor T como un Number — llamar a doubleValue(), intValue() o cualquier método de la API pública de Number.

Úsalo de la misma manera que cualquier otro 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

Sin el límite, sum no podría ni intentar llamar a doubleValue()T se borraría a Object, y Object no tiene ese método. El límite es lo que hace que el cuerpo sea válido.

extends también funciona para interfaces

En los genéricos de Java, extends está sobrecargado — significa "es un subtipo de", ya sea que el límite sea una clase o una interfaz. No existe una palabra clave implements separada en una 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> es una interfaz, pero la sintaxis sigue siendo extends. Lee <T extends Comparable<T>> como "cualquier T que sea comparable consigo mismo" — la restricción es lo que te permite llamar a candidate.compareTo(best) dentro del bucle.

El pequeño detalle aquí es el <T> dentro del límite — es la forma autorreferencial que vimos en interfaces genéricas. Garantiza que compareTo acepte un T, no simplemente cualquier Comparable. Sin esto, podrías comparar Integer con String y el sistema de tipos no lo detectaría.

Límites múltiples

Un parámetro de tipo puede tener más de un límite, 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;
}

Ahora T debe ser tanto un Number como un Comparable<T>. Dentro del cuerpo puedes llamar a cualquier método de ambos. Integer, Long, Double, BigDecimal satisfacen ambos — puedes pasar cualquiera de ellos.

Algunas reglas:

  • El límite de clase (si lo hay) debe ir primero. <T extends Number & Comparable<T>> es válido; <T extends Comparable<T> & Number> no lo es.
  • Como máximo un límite de clase. Java tiene herencia simple para clases, por lo que esto se deriva de las reglas existentes del lenguaje.
  • Cualquier número de límites de interfaz. Apila tantos como necesites de verdad; en la práctica, dos es el máximo habitual antes de que el diseño necesite replantearse.

La regla de la clase primero refleja el bytecode: el borrado reemplaza T con su límite más a la izquierda, por lo que la posición más a la izquierda es especial. Volveremos a eso en Type Erasure.

Límites en parámetros de tipo a nivel de clase

Los límites no son exclusivos de los métodos. Una clase o interfaz genérica puede acotar sus parámetros de tipo en la declaración:

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 es el lugar correcto para un límite que aplica a toda la clase. Cada método, cada campo, cada operación predeterminada tiene acceso a la restricción.

Los límites inferiores pertenecen a los comodines, no a los parámetros de tipo

Una confusión habitual: los parámetros de tipo pueden tener límites superiores (extends) pero no inferiores (super). Esto es válido:

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

Esto no lo es:

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

Los límites inferiores existen en los genéricos de Java — pero solo viven en los comodines, el token ?, no en parámetros de tipo con nombre. Cubriremos la historia completa de ? extends / ? super en el capítulo de Comodines; por ahora, basta con saber que super funciona con ? y no con T.

Cuándo añadir un límite

Lo predeterminado debería ser "sin límite" — cuanto más flexible sea la restricción, más tipos acepta tu método. Añade uno cuando, y solo cuando, el cuerpo necesite llamar a un método específico sobre T:

  • "Necesito comparar valores T" → <T extends Comparable<T>>.
  • "Necesito sumar valores T como números" → <T extends Number>.
  • "Necesito leer campos de T como si fuera un User" → <T extends User>.
  • "Necesito que T sea auto-cerrable para usarlo en try-with-resources" → <T extends AutoCloseable>.

Si no estás llamando a un método sobre T, no necesitas un límite — y añadir uno de todos modos solo restringe tu API sin razón.

Un ejemplo elaborado: un between acotado y un top ordenado

Dos métodos, cada uno con un estilo diferente de límite. between usa Comparable<T> para poder limitar valores; topN usa Number & Comparable<T> para poder tanto comparar como reportar una suma numérica.

java— editable, runs on the server

between acepta cualquier Comparable — incluyendo Character, que es Comparable<Character> y no tiene nada que ver con los números. topN es más restrictivo — necesita tanto ordenamiento (para ordenar) como valores numéricos (para sumar), por eso apila los dos límites con &. Cada método solicita exactamente la capacidad que usa, nada más.

Qué sigue

Un límite en un parámetro de tipo fija un único tipo en el punto de llamada — List<Integer> significa "esta lista, este método, este tipo". A veces quieres ser menos específico: "una lista de algún Number — podría ser Integer, podría ser Double, no importa." Para eso están los comodines, y resuelven una de las mayores fuentes de confusión en el sistema de tipos de Java. Continúa con Comodines genéricos de Java.

Práctica

Práctica
Necesitas un método que encuentre el elemento más grande de una lista. ¿Qué firma te permite llamar a `compareTo` en los elementos sin restringir a los llamadores más de lo necesario?
Necesitas un método que encuentre el elemento más grande de una lista. ¿Qué firma te permite llamar a `compareTo` en los elementos sin restringir a los llamadores más de lo necesario?
Was this page helpful?