Аннотация — это специальная конструкция языка, связанная с классом, методом или переменной, предоставляющая программе дополнительную информацию, на основе которой программа может предпринять дальнейшие действия или реализовать дополнительную функциональность, такую как генерация кода, проверка ошибок и т. д. Помимо использования стандартных аннотаций из пакета java.lang, о которых мы поговорим далее, можно также создавать свои аннотации и обрабатывать их. В этой статье мы обсудим назначение стандартных аннотаций, а также рассмотрим на практическом примере создание и обработку своих аннотаций. Код примеров вы можете найти на GitHub. Аннотации начинаются с символа @. Например, в пакете java.lang определены аннотации @Override и @SuppressWarnings. Сама по себе аннотация не выполняет никаких действий. Она просто предоставляет информацию, которую можно использовать во время компиляции или в рантайме. В качестве примера рассмотрим аннотацию @Override: Аннотация @Override используется для обозначения переопределенного метода из базового класса. Приведенная выше программа при компиляции выдаст ошибку, потому что метод getname() в классе ChildClass аннотирован @Override, но в родительском классе ParentClass метода getname() нет. Используя аннотацию @Override в ChildClass, компилятор проверяет, что имя переопределенного метода в дочернем классе совпадает с именем метода в родительском классе. Рассмотрим некоторые из распространенных стандартных аннотаций из пакета java.lang. Чтобы увидеть их влияние на поведение компилятора, запускайте примеры из командной строки, поскольку большинство IDE могут подавлять предупреждения. Аннотация @SuppressWarnings используется для подавления предупреждений компилятора. Например, @SuppressWarnings("unchecked") отключает  предупреждения, связанные с "сырыми" типами (Raw Types).  Давайте рассмотрим пример использования @SuppressWarnings: Если мы запустим компиляцию из командной строки с параметром -Xlint:unchecked, то получим следующее сообщение: Это пример легаси кода (до Java 5) — в коллекции мы можем случайно сохранить объекты разных типов. Для проверки подобных ошибок на этапе компиляции, были придуманы обобщенные типы (generics, дженерики). Чтобы этот код компилировался без предупреждений измените строку: на Если подобного легаси кода много, то вы вряд ли захотите вносить изменения, поскольку это влечет за собой много регрессионного тестирования. В этом случае к классу можно добавить аннотацию @SuppressWarning, чтобы логи не загромождались избыточными предупреждениями. Теперь при компиляции предупреждений не будет. Аннотация @Deprecated используется для пометки устаревших методов или типов. IDE автоматически обрабатывают эту аннотацию и обычно отображают устаревший метод зачеркнутым шрифтом, сообщая разработчику, что больше не следует его использовать. В примере ниже метод testLegacyFunction() помечен как устаревший: В атрибуте since этой аннотации содержится версия, с которой элемент объявлен устаревшим, а forRemoval указывает, будет ли элемент удален в следующей версии. Теперь вызов устаревшего метода, вызовет предупреждение во время компиляции, указывая, что лучше этот метод не использовать: Мы уже упоминали выше аннотацию @Override. Она используется для проверки переопределенных методов во время компиляции на такие ошибки, как опечатки в регистре символов: Здесь мы хотели переопределить метод getEmployeeStatus(), но неправильно написали имя метода. Это может привести к серьезным ошибкам. Приведенная выше программа скомпилируется и запуститься без проблем, не обнаружив эту ошибку при компиляции. Если добавить аннотацию @Override к методу getemployeeStatus(), то при компиляции получим следующую ошибку: Аннотация @FunctionalInterface используется для указания того, что в интерфейсе не может быть более одного абстрактного метода. Если абстрактных методов будет больше одного, то компилятор выдаст ошибку. Функциональные интерфейсы появились в Java 8 для реализации лямбда-выражений и гарантии того, что в них не более одного абстрактного метода. Но и без аннотации @FunctionalInterface компилятор выдаст ошибку, если вы включите в интерфейс больше одного абстрактного метода. Так зачем же нужна необязательная аннотация @FunctionalInterface? Давайте рассмотрим следующий пример: Если в интерфейс Print мы добавим еще один метод printString2(), то компилятор или IDE выдаст ошибку. А что, если интерфейс Print находится в отдельном модуле и без аннотации @FunctionalInterface? Разработчики этого модуля могут легко добавить в интерфейс еще один метод и сломать ваш код. Добавив аннотацию @FunctionalInterface, мы сразу получим предупреждение в IDE: Поэтому рекомендуется всегда использовать аннотацию @FunctionalInterface, если интерфейс должен использоваться в качестве лямбды. Функциональность varargs позволяет создавать методы с переменным количеством аргументов. До Java 5 единственной возможностью создания методов с необязательными параметрами было создание нескольких методов, каждый из которых с разным количеством параметров. Varargs позволяет создать один метод с переменным количеством параметров с помощью следующего синтаксиса: Однако при использовании в аргументах метода обобщенных типов выдаются предупреждения. Аннотация @SafeVarargs позволяет подавить их: Методы printString() и printStringVarargs() приводят к одинаковому результату. Но при компиляции для метода printStringSafeVarargs() выдается предупреждение, поскольку в нем используются обобщенные типы: Добавив аннотацию @SafeVarargs, мы можем избавиться от этого предупреждения: Мы можем создавать свои аннотации, например, для реализации следующей функциональности: Уменьшение дублирования кода. Автоматизация генерации бойлерплейт кода. Отлов ошибок во время компиляции, например, потенциальные Null Pointer Exception. Настройка поведения в рантайме на основе наличия аннотации. Для примера рассмотрим аннотацию @Company: При создании экземпляров класса CustomAnnotatedEmployee все экземпляры будут содержать одно и то же название компании (name) и города (city) — больше не нужно добавлять эту информацию в конструктор. Создать пользовательскую аннотацию можно с помощью ключевого слова @interface: Чтобы указать информацию об области действия аннотации и о типах элементов, к которым она может быть применена, используются мета-аннотации. Например, чтобы указать, что аннотация применяется только к классам, используется аннотация @Target(ElementType.TYPE). А мета-аннотация @Retention(RetentionPolicy.RUNTIME) указывает, что аннотация должна быть доступна в рантайме. С мета-аннотациями наша аннотация @Company выглядит следующим образом: Далее добавим атрибуты в нашу аннотацию: имя (name) и город (city). Добавляем их, как показано ниже: Создадим класс CustomAnnotatedEmployee и применим к нему аннотацию @Company: Прочитать аннотацию @Company в рантайме можно следующим образом: Результат будет следующий: Анализируя аннотацию в рантайме, мы можем получить доступ к некоторой общей информации обо всех сотрудниках и избежать дублирования кода. Мета-аннотации — это аннотации, применяемые к другим аннотациям для предоставления информации об аннотации компилятору или среде выполнения. Мета-аннотации могут ответить на следующие вопросы об аннотации: Может ли аннотация наследоваться дочерними классами? Должна ли аннотация отображаться в документации? Можно ли применить аннотацию несколько раз к одному и тому же элементу? К какому типу элементов можно применить аннотацию: к классу, методу, полю и т.д.? Обрабатывается ли аннотация во время компиляции или в рантайме? По умолчанию аннотация не наследуется от родительского класса к дочернему. Мета-аннотация @Inherited позволяет ей наследоваться: Поскольку CustomAnnotatedEmployee аннотирован @Company, а CustomAnnotatedManager наследуется от него, то нет необходимости ставить аннотацию на класс CustomAnnotatedManager. Давайте проверим это. Аннотация @Company доступна, хотя мы не указывали ее явно для класса Manager. @Documented указывает, что аннотация должна присутствовать в JavaDoc. По умолчанию информация об аннотациях не отображается в JavaDoc-документации, но если использовать @Documented, она появится: @Repeatable позволяет использовать аннотацию несколько раз на одном методе, классе или поле. Для использования @Repeatable — аннотации необходимо создать аннотацию-контейнер, которая хранит значение в виде массива исходных аннотаций:} Использовать аннотацию можно следующим образом: Протестируем: Получим следующий результат, отображающий значение нескольких аннотаций @RepeatableCompany: @Target определяет типы элементов, к которым может применяться аннотация. Например, в приведенном выше примере аннотация @Company была определена как TYPE, и поэтому может быть применена только к классам. Давайте попробуем применить аннотацию @Company к методу: В этом случае мы получим ошибку компилятора: @Company not applicable to method. Существуют следующие типы целей, названия которых говорят сами за себя: ElementType.ANNOTATION_TYPE ElementType.CONSTRUCTOR ElementType.FIELD ElementType.LOCAL_VARIABLE ElementType.METHOD ElementType.PACKAGE ElementType.PARAMETER ElementType.TYPE @Retention указывает, когда аннотация будет доступна: SOURCE — аннотация доступна в исходном коде и удаляется после компиляции. CLASS — аннотация сохраняется в class-файле во время компиляции, но недоступна при выполнении программы. RUNTIME — аннотация доступна в рантайме. Если аннотация нужна только для проверки ошибок во время компиляции, как это делает @Override, мы используем SOURCE. Если аннотация нужна для обеспечения функциональности в рантайме, например, @Test в JUnit, то используем RUNTIME. Давайте поэкспериментируем с разными значениями RetentionPolicy: Создадим класс, который использует все три аннотации: Для проверки доступности аннотаций запустите следующий код: Результат будет следующим: Итак, мы убедились, что в рантайме доступна только RUNTIME-аннотация. Аннотации можно классифицировать по количеству передаваемых в них параметров: без параметров, с одним параметром и с несколькими параметрами. Маркерные аннотации не содержат никаких членов или данных. Для определения наличия аннотации можно использовать метод isAnnotationPresent(). Например, если бы у нашей компании было несколько клиентов с разными способами передачи данных, мы могли бы аннотировать класс аннотацией, указывающей способ передачи данных: Класс Client может использовать аннотацию следующим образом: Обработать аннотацию можно следующим образом: На основании присутствия аннотации @CSV, мы можем решить, куда записать информацию — в CSV или в файл Excel. Приведенная выше программа выдаст следующий результат: Аннотации с одним значением содержат только один атрибут, который принято называть value. Давайте создадим аннотацию SingleValueAnnotationCompany с одним атрибутом value: Создайте класс, использующий аннотацию: Запустите следующий пример: Переданное значение "XYZ" переопределяет значение атрибута аннотации по умолчанию. Результат выглядит следующим образом: Они состоят из нескольких пар "имя-значение". Например, Company(name = "ABC", city = "XYZ"). Рассмотрим наш исходный пример Company: Давайте создадим класс MultiValueAnnotatedEmployee со значением параметров, как показано ниже. Значения по умолчанию будут перезаписаны. Запустите следующий пример: Результат: В качестве практического примера обработки аннотаций напишем простой аналог аннотации @Test из JUnit. Пометив методы аннотацией @Test, мы сможем определить в рантайме, какие методы тестового класса нужно запускать как тесты. Сначала создадим маркерную аннотацию для методов-тестов: Далее создадим класс AnnotatedMethods, в котором применим аннотацию @Test к методу test1(). Это позволит выполнить метод в рантайме. У метода test2() аннотации нет и он не должен выполняться. Теперь напишем код для запуска тестов из класса AnnotatedMethods: Через метод getDeclaredMethods() мы получаем методы класса AnnotatedMethods. Затем перебираем методы и проверяем, аннотирован ли метод аннотацией @Test. Наконец, выполняем вызов методов, которые были аннотированы с помощью @Test. В результате метод test1() выполнится, поскольку он аннотирован @Test, а test2() нет, так как он без аннотации @Test. Результат: Мы сделали обзор основных стандартных аннотаций и рассмотрели, как создавать и обрабатывать свои аннотации. Возможностей по использованию аннотаций гораздо больше, чем мы рассмотрели. Например, можно автоматически генерировать код для паттерна Builder. Шаблон проектирования Builder (строитель) используется как альтернатива конструкторам, когда в конструкторы передается много параметров или есть необходимость в нескольких конструкторах с необязательными параметрами. При большом количестве таких классов  возможность генерации кода обработчиком аннотаций сэкономит много времени и поможет избежать дублирования кода. Примеры кода вы можете найти на GitHub. Всех желающих приглашаем на Demo-занятие «Объектно-ориентированное и функциональное программирование». На вебинаре поговорим о стилях программирования и необходимости каждого из них. Разберём основные принципы объектно-ориентированного стиля (Инкапсуляция, Наследование, Полиморфизм), а также возможности функционального стиля, которые предоставляет язык Java. Регистрация для всех желающих по ссылке. Переводчик, редактор