Примечания
Статья написана на основе курса от компании Oracle "Learning the Java language", раздела "Annotations".
Статья подойдет тем, кто встречался с аннотациями на практике и хочет углубить или освежить теоретические знания.
Основы аннотаций
Определение
Аннотации - это форма метаданных. Они предоставляют информацию о программе, при том сами частью программы не являются.
Применение
Информация для компилятора. Могут использоваться компилятором для обнаружения ошибок и подавления предупреждений.
Обработка во время компиляции и развертывания. Программа может создавать код, XML-файлы и т.п. на основе аннотаций.
Обработка во время выполнения. Некоторые аннотации могут использоваться во время выполнения программы.
Синтаксис
Начинаются с @
, могут включать элементы, которым присваиваются значения:
@Author(
name = "Benjamin Franklin"
date = "3/27/2003"
)
class MyClass{...}
Если такой элемент один, его имя можно опустить:
@SupressWarnings("unchecked")
void MyMethod() {...}
Если таких элементов нет, можно опустить скобки. Можно использовать несколько аннотаций в одном объявлении:
@Author(name = "Jane Doe")
@EBook
class MyClass { ... }
Аннотации могут быть повторяющимися.
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }
Где в коде можно использовать аннотации
Аннотации применяются с объявлениями классов, полей и других элементов программы.
Аннотации, использующиеся с типами, называются аннотациями типов. Примеры таких аннотаций:
Создание экземпляра класса:
new @Interned MyObject();
Приведение к типу:
myString = (@NonNull String) str;
Имплементация:
class UnmodifiableList<T>
implements @Readonly List<@Readonly T> { ... }
Объявление бросаемых исключений:
void monitorTemperature() throws
@Critical TemperatureException { ... }
Создание аннотации
Синтаксис
Описание аннотации напоминает описание интерфейса. Оно начинается с @Interface
, а его элементы похожи на методы, которые могут иметь дефолтные значения.
Пример
Допустим, в какой-то IT-компании тела всех классов начинаются с комментариев, содержащих важную информацию:
public class Generation3List extends Generation2List {
// Author: John Doe
// Date: 3/17/2002
// Current revision: 6
// Last modified: 4/12/2004
// By: Jane Doe
// Reviewers: Alice, Bill, Cindy
// class code goes here
}
Описание аннотации, которая заменит комментарии:
@interface ClassPreamble {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Можно использовать массив
String[] reviewers();
}
Использование созданной аннотации:
@ClassPreamble (
author = "John Doe",
date = "3/17/2002",
currentRevision = 6,
lastModified = "4/12/2004",
lastModifiedBy = "Jane Doe",
reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {
...
...
}
Замечание: для добавления аннотации в Javadocs нужно использовать @Documented:
import java.lang.annotation.*;
@Documented
@interface ClassPreamble {
// Описание элементов аннотации
}
Предопределенные аннотации
В Java есть аннотации, описанные заранее. Часть из них предоставляют информацию для компилятора, часть применяется к другим аннотациям.
Аннотации, использующиеся компилятором
Располагаются в пакете java.lang.
Помеченный этой аннотацией элемент устарел и больше не должен использоваться (это стоит отметить в Javadoc). При наличии такого элемента в программе компилятор сгенерирует предупреждение.
// Комментарий Javadoc:
/**
* @deprecated
* объяснение, почему метод устарел.
*/
@Deprecated
static void deprecatedMethod() { }
Информирует компилятор о том, что аннотируемый элемент должен переопределять элемент родительского класса. При некорректном переопределении компилятор сгенерирует ошибку.
@Override
int overriddenMethod() { }
Подавляет генерируемые компилятором предупреждения.
Предупреждения делятся на непроверенные (unchecked) и устаревшие (deprecation). Первые возникают при использовании устаревшего кода, написанного до дженериков, вторые - при использовании кода, помеченного аннотацией @Deprecated.
Можно подавить как одну категорию, так и обе сразу:
@SuppressWarnings({"unchecked", "deprecation"})
Применяется к методу или конструктору и утверждает, что код не выполняет потенциально небезопасных операций с параметрами varargs. При использовании аннотации подавляются unchecked предупреждения, связанные с varargs.
@SafeVarargs // На самом деле не безопасно!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; //Написано неверно, но скомпилируется без предупреждения
String s = stringLists[0].get(0); //ClassCastException
}
Используется при описании функционального интерфейса. Подчеркивает, что это именно функциональный интерфейс.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Аннотации, применимые к другим аннотациям (мета-аннотации)
Располагаются в пакете java.lang.annotation.
Указывает, сколько хранится отмеченная аннотация.
RetentionPolicy.SOURCE
. Отмеченная аннотация сохраняется только на уровне исходного кода и игнорируется компилятором.RetentionPolicy.CLASS
. Сохраняется компилятором во время компиляции, но игнорируется JVM.RetentionPolicy.RUNTIME
. Сохраняется JVM для использования во время выполнения программы.
Указывает, что аннотация, должна быть задокументирована в Javadoc (по умолчанию аннотации не документируются).
Определяет права доступа аннотации (к каким элементам ее можно применять). В аннотации @Target
указывается одно из следующих значений:
ElementType.ANNOTATION_TYPE
. Применяется к аннотацииElementType.CONSTRUCTOR
. Применяется к конструктору.ElementType.FIELD
. Применяется к полю или свойству.ElementType.LOCAL_VARIABLE
. Применяется к локальной переменной.ElementType.METHOD
. Применяется к методу.ElementType.PARAMETER
. Применяется к параметру метода.ElementType.TYPE
. Применяется к любому элементу класса.
Аннотация будет наследоваться дочерним классом (по умолчанию аннотации не наследуются). Применима только к описаниям классов.
Указывает, что аннотация повторяющаяся.
Повторяющиеся аннотации
Определение
Аннотации, которые могут применяться к одному и тому же элементу более одного раза.
Пример
Допустим, вам надо написать аннотацию, запускающую метод в заданное время или по определенному расписанию. В примере созданная аннотация @Schedule
будет запускать метод каждый последний день месяца и каждую пятницу в 23:00.
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }
Создание повторяющейся аннотации
Для обеспечения обратной совместимости повторяющиеся аннотации хранятся в контейнере аннотаций, который автоматически генерируется java компилятором. Для генерации нужны следующие описания:
Описание повторяющейся аннотации
Аннотация должна быть помечена @Repeatable, в скобках указан тип контейнера аннотаций.
import java.lang.annotation.Repeatable;
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}
Описание контейнера аннотаций
Контейнер должен содержать массив повторяющихся аннотаций.
public @interface Schedules {
Schedule[] value();
}
Получение повторяющихся аннотаций
Reflection API предоставляет методы для получения аннотаций. При получении повторяющихся аннотаций поведение методов, которые возвращают одну аннотацию (например, AnnotatedElement.getAnnotation(Class<T>)
) не меняется. Если нужно вернуть более одной, то необходимо сначала получить контейнер. Таким образом устаревший код продолжает работать. Также, для получения повторяющихся аннотаций можно использовать методы Java SE 8 ('AnnotatedElement.getAnnotationsByType(Class)').
Для получения информации о всех методах, обратитесь к документации класса AnnotatedElement.
Аннотации типов
Определение
Аннотации типов - аннотации, которые применяются вместе с типами. Везде, где вы видите тип, можно использовать эту аннотацию. Например, с оператором new, при приведении, при имплементации и при использовании throws.
Создание аннотации типов
Для создания аннотации типов в @Target
указываются следующие значения, либо одно из них:
@Target ({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public @interface Test {
}
TYPE_PARAMETER
, означает, что аннотацию можно применять к переменным типа (например, MyClass<T>
). TYPE_USE
, разрешает применение с любыми типами.
Применение
Аннотации типов предназначены для улучшенного анализа программ и более строгой проверки типов. Например, @NonNull String str;
. Java SE8 определяет аннотации типов, но не реализует. Вместо этого предлагается использовать сторонние фреймворки, реализующие их (Checker Framework).
Комментарии (7)
anaken
21.04.2022 08:46Аннотации - это форма метаданных. Они предоставляют информацию о программе, при том сами частью программы не являются.
Откуда такое определение пошло ? В любом случае, применительно к java - оно не верно. Аннотации в наше время используются все чаще, притом именно в ходе выполнения программы. Не зря для этого есть целый набор для работы с аннотациями.
AotD
Как абсолютный 0 в Java интересно - придумал ли кто-то аннотацию которая выкидывает из результирующего кода класс/метод при компиляции с определенными параметрами?
Например нужно собирать несколько версий библиотеки из одной кодовой базы. Релизный для разработчиков и debug для тестировщиков. Для релизного нужно выкинуть методы манипулирования с сырыми данными.
Разметить бы из каким-нибудь @DebugOnlyи вычищать в релизных сборках.
Skynet2034
Можно юзать разные Spring-профили. Дебажные методы вынести в отдельные бины и поставить им профиль, отличающийся от prod.
Norgorn
В java есть package-private методы, обычно их достаточно для таких случаев. Ну или унаследовать класс и сделать нужные методы публичными, некрасиво, но для тестов пойдёт. Или просто вызывать их с помощью рефлексии - тоже только для тестов вполне себе решение.
Arty_Fact
Нельзя унаследоваться от класса и «сделать нужные методы публичными». Дочерний класс не знает ничего о приватных методах родительского класса. То есть конечно можно в дочернем классе создать абсолютно такой же метод как в родительском, но это не будет переопределением. Можете поставить Override и убедиться.
Norgorn
Ну да, неоднозначно получилось, так просто нельзя, плохо написал.