W3docs

Ограниченные параметры типа в Java

Ограничение обобщённых типов Java с помощью bounded type parameters и extends — одиночные и множественные границы.

Ограниченный параметр типа — это параметр типа с конструкцией extends, ограничивающей допустимые типы-заменители. Без ограничения T рассматривается как Object — компилятор не знает, есть ли у T методы compareTo, intValue или какие-либо другие. С ограничением вы даёте компилятору обещание о том, чем является T, и в ответ компилятор позволяет вызывать методы, которые следуют из этого обещания. Практически каждый интересный обобщённый метод имеет хотя бы одно ограничение где-нибудь в сигнатуре.

Базовый синтаксис: <T extends Bound>

Синтаксис — ключевое слово extends между параметром и его ограничением — одно и то же ключевое слово вне зависимости от того, является ли ограничение классом или интерфейсом:

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

Читайте <T extends Number> как «любой T, который является Number или его подклассом». Внутри тела можно обращаться с любым значением T как с Number — вызывать doubleValue(), intValue(), любой метод из публичного API Number.

Используйте так же, как любой другой обобщённый метод:

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

Без ограничения sum вообще не смог бы попытаться вызвать doubleValue()T был бы стёрт до Object, у которого этого метода нет. Именно ограничение делает тело метода законным.

extends работает и для интерфейсов

В обобщённых типах Java ключевое слово extends перегружено — оно означает «является подтипом», будь то класс или интерфейс. Отдельного ключевого слова implements в списке параметров типа нет:

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> — это интерфейс, но синтаксис по-прежнему использует extends. Читайте <T extends Comparable<T>> как «любой T, сравнимый сам с собой» — именно это ограничение позволяет вызывать candidate.compareTo(best) внутри цикла.

Небольшая тонкость здесь — <T> внутри ограничения: это саморекурсивная форма, которую мы встречали в разделе про обобщённые интерфейсы. Она обеспечивает, что compareTo принимает именно T, а не произвольный Comparable. Без неё можно было бы сравнивать Integer со String, и система типов этого не заметила бы.

Множественные ограничения

Параметр типа может иметь более одного ограничения; они объединяются с помощью &:

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

Теперь T должен быть одновременно Number и Comparable<T>. Внутри тела можно вызывать любые методы из обоих. Integer, Long, Double, BigDecimal удовлетворяют обоим условиям — можно передать любой из них.

Несколько правил:

  • Ограничение-класс (если есть) должно стоять первым. <T extends Number & Comparable<T>> допустимо; <T extends Comparable<T> & Number> — нет.
  • Не более одного ограничения-класса. В Java одиночное наследование классов, так что это вытекает из правил самого языка.
  • Ограничений-интерфейсов может быть сколько угодно. Добавляйте столько, сколько действительно нужно; на практике два — обычный максимум, после которого стоит пересмотреть дизайн.

Правило «класс — первым» связано с байт-кодом: стирание заменяет T его самым левым ограничением, поэтому крайняя левая позиция особенная. Подробнее об этом — в разделе Type Erasure.

Ограничения на параметры типа уровня класса

Ограничения не уникальны для методов. Обобщённый класс или интерфейс может ограничивать свои параметры типа в объявлении:

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>

Это правильное место для ограничения, применимого ко всему классу. Каждый метод, каждое поле, каждая операция по умолчанию имеет доступ к этому ограничению.

Нижние ограничения относятся к wildcard-ам, а не к параметрам типа

Распространённая путаница: параметры типа могут иметь верхние ограничения (extends), но не нижние (super). Это допустимо:

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

А это — нет:

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

Нижние ограничения в обобщённых типах Java существуют — но только в wildcard-ах, токене ?, а не в именованных параметрах типа. Полную историю ? extends / ? super мы рассмотрим в разделе Wildcards; пока достаточно знать, что super работает с ?, но не с T.

Когда добавлять ограничение

По умолчанию ответ — «без ограничения»: чем слабее ограничение, тем больше типов принимает метод. Добавляйте ограничение тогда и только тогда, когда тело метода должно вызвать конкретный метод у T:

  • «Мне нужно сравнивать значения T» → <T extends Comparable<T>>.
  • «Мне нужно складывать значения T как числа» → <T extends Number>.
  • «Мне нужно читать поля T как у User» → <T extends User>.
  • «Мне нужно, чтобы T реализовывал auto-closeable для использования в try-with-resources» → <T extends AutoCloseable>.

Если вы не вызываете метод у T, ограничение не нужно — добавление ограничения без причины лишь сужает ваш API.

Практический пример: ограниченный between и отсортированный top

Два метода, каждый с ограничением своего стиля. between использует Comparable<T> для сужения диапазона; topN использует Number & Comparable<T>, чтобы и сортировать, и считать числовую сумму.

java— editable, runs on the server

between принимает любой Comparable — включая Character, который является Comparable<Character> и не имеет ничего общего с числами. topN уже, так как ему нужны и упорядочивание (для сортировки), и числовые значения (для суммы), поэтому он объединяет два ограничения через &. Каждый метод запрашивает ровно те возможности, которые использует, — не больше.

Что дальше

Ограничение на параметр типа фиксирует конкретный тип в месте вызова — List<Integer> означает «этот список, этот метод, этот тип». Иногда хочется быть менее конкретным: «список какого-то Number — может быть Integer, может быть Double, неважно». Для этого и существуют wildcards, которые устраняют один из самых распространённых источников путаницы в системе типов Java. Продолжайте читать: Java Generic Wildcards.

Практика

Практика
Вам нужен метод, находящий наибольший элемент списка. Какая сигнатура позволяет вызывать `compareTo` для элементов, не ограничивая вызывающий код сильнее необходимого?
Вам нужен метод, находящий наибольший элемент списка. Какая сигнатура позволяет вызывать `compareTo` для элементов, не ограничивая вызывающий код сильнее необходимого?
Was this page helpful?