W3docs

Встроенные аннотации Java

Встроенные аннотации Java — @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.

Стандартная библиотека поставляется с небольшим набором аннотаций в java.lang, которые компилятор обрабатывает особым образом. Ни одна из них не добавляет поведения в ваш код во время выполнения — все они являются подсказками для javac (или, в случае @Deprecated, для инструментальной цепочки в целом). Понимание того, что обещает каждая из них — и чего она не обещает — это и есть практическая сторона работы с аннотациями в повседневных задачах.

Пять аннотаций, которые вы будете встречать чаще всего:

  • @Override — «этот метод переопределяет метод суперкласса.»
  • @Deprecated — «не используйте это — API будет удалён.»
  • @SuppressWarnings — «подавить определённые предупреждения в этой области видимости.»
  • @SafeVarargs — «мой метод с varargs не загрязняет кучу.»
  • @FunctionalInterface — «этот интерфейс содержит ровно один абстрактный метод.»

@Override

Размещение @Override на методе сообщает компилятору, что данный метод предназначен для переопределения метода суперкласса или реализации метода интерфейса. Если метод на самом деле ничего не переопределяет (из-за опечатки в имени, неправильной сигнатуры или удаления метода из родительского класса), javac завершает сборку с ошибкой.

class Animal {
  public String speak() { return "?"; }
}

class Dog extends Animal {
  @Override
  public String speak() { return "woof"; }                 // OK

  @Override
  public String spaek() { return "oops"; }                 // compile error: nothing to override
}

Эта аннотация позволяет обнаружить ошибки, которые очень сложно найти во время выполнения: переопределяющий метод, сигнатура которого расходится с суперклассом. Всегда пишите @Override на методах, которые вы намеревались переопределить. Аннотация имеет уровень хранения SOURCE, поэтому она исчезает в момент завершения компиляции.

@Deprecated

@Deprecated помечает элемент — класс, метод, поле, конструктор — как нежелательный к использованию. Компилятор выдаёт предупреждение в каждом месте использования. Начиная с Java 9 аннотация принимает два элемента:

@Deprecated(since = "1.4", forRemoval = true)
public void oldApi() { ... }
  • since документирует, когда было введено устаревание.
  • forRemoval = true является более сильным сигналом: в будущем выпуске планируется удаление API. Компилятор выдаёт предупреждение об удалении (как правило, более заметное, чем обычное предупреждение об устаревании), а инструмент Javadoc помечает его иначе.

В отличие от @Override, аннотация @Deprecated имеет уровень хранения RUNTIME — инструменты для работы с байткодом, IDE и рефлексия могут её видеть. Сопутствующий тег Javadoc @deprecated (строчными буквами, в комментарии к документации) содержит объяснение; аннотация активирует инструментарий.

@SuppressWarnings

Когда компилятор прав почти всегда, но не в данном конкретном случае, @SuppressWarnings подавляет категорию предупреждений внутри аннотированного элемента:

@SuppressWarnings("unchecked")
List<String> strings = (List<String>) raw;               // raw cast intentional

@SuppressWarnings({"unchecked", "rawtypes"})
public void uglyButNeeded() { ... }

Строковый элемент задаёт категорию предупреждения; наиболее распространённые: unchecked, rawtypes, deprecation, serial, unused, removal. Компиляторы могут принимать и другие. Два правила для поддержания чистоты кода:

  1. Аннотируйте минимально возможную область видимости. Подавляйте предупреждение для локальной переменной или одного метода, но никогда — для всего класса, если только действительно каждая строка в нём нуждается в этом.
  2. Добавляйте комментарий, объясняющий причину. Голый @SuppressWarnings("unchecked") рядом с приведением типа оставляет следующего читателя в недоумении — а безопасно ли это приведение на самом деле?

@SafeVarargs

Метод с varargs, чей тип параметра содержит параметр типа, имеет тонкую проблему: на месте вызова компилятор может быть вынужден создать массив обобщённого типа, что небезопасно. Компилятор предупреждает об этом сообщением «possible heap pollution». Если автор убедился, что тело метода не допускает утечки массива и не записывает в него неправильные типы, @SafeVarargs подавляет это предупреждение:

@SafeVarargs
public final <T> List<T> listOf(T... items) {
  return java.util.List.of(items);                        // only reads the items, never stores other types
}

Правила:

  • Применимо только к методам, которые нельзя переопределить — static, final, или private, а также к конструкторам в стиле record.
  • Аннотация является обещанием. Если тело метода действительно записывает в массив varargs значение неправильного типа, приведение типа на месте вызова может позже завершиться с непонятным ClassCastException.

Уровень хранения — SOURCE.

@FunctionalInterface

Функциональный интерфейс — это интерфейс с ровно одним абстрактным методом; такую форму имеют Runnable, Callable, Comparator и Function. Лямбда-выражения и ссылки на методы нацелены именно на функциональные интерфейсы. Аннотация делает намерение явным и предписывает компилятору соблюдать правило единственного абстрактного метода:

@FunctionalInterface
public interface StringMapper {
  String map(String input);                              // the single abstract method

  default StringMapper andThen(StringMapper next) {       // default methods are allowed
    return s -> next.map(map(s));
  }
}

Если впоследствии добавить второй абстрактный метод, сборка немедленно завершится с ошибкой. Без аннотации интерфейс незаметно перестал бы быть пригодным в качестве цели для лямбды — что пользователь заметил бы только на месте вызова.

Как и @Override, имеет уровень хранения SOURCE.

Практический пример: наблюдение за контролем компилятора

Эта программа демонстрирует четыре аннотации с принуждением компилятором и показывает, что доступно среде выполнения после компиляции. Интересные моменты: метод с @SafeVarargs компилируется без предупреждений, тогда как без аннотации выводилось бы предупреждение; @FunctionalInterface отражается через правила подсчёта SAM; значения элементов @Deprecated доступны во время выполнения.

java— editable, runs on the server

Что можно почерпнуть из результатов выполнения:

  • @FunctionalInterface выполнила свою задачу на этапе компиляции, гарантировав, что StringMapper является SAM-типом: String::toUpperCase и лямбда s -> s + \"!\" оба привязались к нему без проблем. Если кто-то добавит второй абстрактный метод в интерфейс, компиляция завершится ошибкой и эти выражения перестанут разрешаться.
  • Строка с @Override — это гарантия того, что Child.describe() действительно переопределяет Parent.describe(). Полиморфный вызов, вернувший \"child\", подтверждает это; если бы сигнатура расходилась (другое имя, другой тип возврата), сборка завершилась бы ошибкой вместо неправильного поведения во время выполнения.
  • @Deprecated — единственная аннотация здесь, которая сохраняется после компиляции. Рефлексия успешно извлекла since=1.4 и forRemoval=true из файла класса. Сам метод по-прежнему выполнился — @Deprecated предупреждает, но не отключает.
  • @SafeVarargs устранила предупреждение компилятора «possible heap pollution» при сохранении типобезопасного вызова. Обратите внимание, что метод является static, что удовлетворяет правилу «не может быть переопределён». Удаление аннотации скомпилировалось бы, но при этом javac выдал бы предупреждение; добавление её к нестатическому, не-final, не-private методу стало бы ошибкой компиляции.
  • @SuppressWarnings не оставила следов во время выполнения — массив аннотаций, выведенный для parseOrZero, пуст. В этом и заключается весь смысл уровня хранения SOURCE: аннотация выполняет свою работу во время компиляции, а затем исчезает, не загромождая файл класса.

Другие встроенные аннотации, которые стоит знать

Несколько менее распространённых аннотаций из стандартной библиотеки, вкратце:

  • @SuppressWarnings("preview") — для кода, использующего предварительные функции языка (Java 14+).
  • @Native (java.lang.annotation.Native) — помечает константу, на которую может ссылаться нативный код; используется инструментами, генерирующими заголовки JNI.
  • @Generated (javax.annotation.processing.Generated, Java 9+) — добавляется генераторами кода в файлы, которые они создают.
  • @Documented, @Retention, @Target, @Inherited, @Repeatable — это мета-аннотации; рассматриваются в следующей главе.

Первые три вы редко будете писать вручную. Мета-аннотации — это вход в мир создания собственных типов аннотаций, а рефлексия позволяет читать обратно аннотации с уровнем хранения RUNTIME, такие как @Deprecated — как показывает приведённый выше пример.

Практика

Практика
Метод объявлен как `public <T> T[] toArray(T... values)`, и компилятор предупреждает о 'possible heap pollution from parameterized vararg type'. Автор изучает тело метода, убеждается, что в массив записываются только элементы типа T, и добавляет `@SafeVarargs`. Почему компилятор отвергает аннотацию?
Метод объявлен как `public <T> T[] toArray(T... values)`, и компилятор предупреждает о 'possible heap pollution from parameterized vararg type'. Автор изучает тело метода, убеждается, что в массив записываются только элементы типа T, и добавляет `@SafeVarargs`. Почему компилятор отвергает аннотацию?
Was this page helpful?