Аннотации в Java. Не путать с комментариями
Аннотации в Java – зачем они нужны
Аннотации – это пометки, с помощью которых программист указывает компилятору Java и средствам разработки, что делать с участками кода помимо исполнения программы. Аннотировать можно переменные, параметры, классы, пакеты. Можно писать свои аннотации или использовать стандартные – встроенные в Джава.
Вы узнаете аннотацию по символу @ в начале имени: @Override – стандартная аннотация Javа, которая предупреждает, что ниже мы что-то переопределим:
Если в имени метода из AnotherClass будет опечатка, компилятор учтет @Override и выдаст ошибку. Без аннотации он не заметил бы подвоха и безропотно создал бы новый метод в дополнение к method из SomeClass.
Обратите внимание, сама аннотация никак не влияет на переопределение метода, но позволяет контролировать успешность переопределения при компиляции или сборке. Мы защитили участок кода от неприметной ошибки, на поиск которой в большой программе ушли бы часы. Это лишь одно из многих применений аннотаций.
Зачем нужны аннотации Java
Поясним понятие «маркерный интерфейс». Интерфейсы без каких-либо методов действуют как маркеры. Они лишь говорят компилятору, что объекты классов, которые имплементируют такой интерфейс без методов, должны иметь отличительные черты, восприниматься иначе. Например, java.io.Serializable, java.lang.Cloneable, java.util.EventListener. Маркерные интерфейсы ещё известны как «теги» — они добавляют общий тег ко всем унаследованным классам и объединяют их в одну категорию.
При первом появлении в Java EE 5 аннотации были представлены как инструмент, который ускоряет разработку больших web-сервисов и клиентских приложений. Как это работает?
Обработка аннотации в Джава
На основе аннотаций компилятор может с помощью специальных обработчиков генерировать новый код и файлы конфигурации.
Обработчиками обычно выступают библиотеки и утилиты, которые можно брать у сторонних авторов (или создавать самостоятельно) и прикреплять к проекту в среде разработки. Способ подключения зависит от IDE или системы сборки. В Maven обработчики подключают с помощью модуля annotation-user или плагина maven-compiler-plugin.
Парсинг аннотаций происходит циклически. Компилятор ищет их в пользовательском коде и выбирает подходящие обработчики. Если вызванный обработчик на основе аннотации создаёт новые файлы с кодом, начинается следующий этап, где исходным материалом становится сгенерированный код. Так продолжается до тех пор, пока не будут созданы все необходимые файлы.
Пишем первую аннотацию на Java
Допустим, у нас есть веб-сервис, который поддерживает несколько версий одного функционала для соблюдения совместимости. И есть обработчик аннотаций, который позволяет компилятору выбирать нужные версии. На минутку забудем о существовании Git 🙂
Где хранить данные о версии и авторе функционала? Конечно же, в аннотации. Напишем её. Новую аннотацию объявляют с помощью ключевого слова @interface:
Это немного искусственный, но зато простой и наглядный пример аннотации на Java. Мы добавили два атрибута, которые выглядят как методы. Отличие в том, что при объявлении атрибутов никогда не используют оператор throws и не назначают параметров. Значениями могут выступать:
Можно указывать значения по умолчанию, что мы и сделали выше с полем author. При постановке аннотации атрибуты с дефолтными значениями можно пропускать.
Чтобы нашу аннотацию использовали только по назначению, вернёмся к её объявлению и укажем, где и когда она должна работать:
Остается ассоциировать аннотацию с нужным классом и запустить программу.
Используем аннотации Java для сравнения баз данных
Мы наметили конфигурацию работы с данными, остаётся её реализовать. Вот здесь на сцену выходят аннотации. С их помощью мы можем задать (а если потребуется — и скорректировать) способы обработки каждого поля. При этом конфигурация будет выглядеть абсолютно прозрачно: читатель кода сразу поймёт, что к чему применено. И ход исполнения программы не будет затронут. Волшебно! Пишем:
Теперь ассоциируем аннотацию с полем. Допустим, у нас в БД книги:
Присвоили id, присвоили имя полю в отчёте. Сверка значений со всеми базами запустится по умолчанию, т.к мы не указали для compareSources значение false. А если мы заведомо знаем, что искать нужно не во всех БД, а в конкретных? Например, сюжет есть не у любой книги. Задаём источники вручную:
Если у нас есть поле «Примечания», значение которого не нужно сверять, мы можем перед переменной для хранения примечаний поставить такую аннотацию:
Неплохо, но как сделать, чтобы правила обработки выбирались в зависимости от значения поля? Можно связать правила с классом обработчика, чтобы тот выбирал, как поступать с каждым полем. Для этого потребуется ещё одна короткая аннотация:
Проверим, как это работает. Давайте не сравнивать данные о литературе, которая поступила в библиотеку до 1990 года — допустим, эти данные у нас были только в одной базе:
Теперь реагировать на значения менее 1990 в поле «год поступления» мы доверяем конкретному пользовательскому классу. Это даёт нам больше гибкости в работе с данными, но помните, что аннотации — вещь статическая. Какое значение получил обработчик на входе, с тем и разбирается. Интерактива с динамической сменой значений пользователем не получится.
Аннотации в Java – зачем они нужны
Аннотации – это пометки, с помощью которых программист указывает компилятору Java и средствам разработки, что делать с участками кода помимо исполнения программы. Аннотировать можно переменные, параметры, классы, пакеты. Можно писать свои аннотации или использовать стандартные – встроенные в Джава.
Вы узнаете аннотацию по символу @ в начале имени: @Override – стандартная аннотация Javа, которая предупреждает, что ниже мы что-то переопределим:
Если в имени метода из AnotherClass будет опечатка, компилятор учтет @Override и выдаст ошибку. Без аннотации он не заметил бы подвоха и безропотно создал бы новый метод в дополнение к method из SomeClass.
Обратите внимание, сама аннотация никак не влияет на переопределение метода, но позволяет контролировать успешность переопределения при компиляции или сборке. Мы защитили участок кода от неприметной ошибки, на поиск которой в большой программе ушли бы часы. Это лишь одно из многих применений аннотаций.
Зачем нужны аннотации Java
Поясним понятие «маркерный интерфейс». Интерфейсы без каких-либо методов действуют как маркеры. Они лишь говорят компилятору, что объекты классов, которые имплементируют такой интерфейс без методов, должны иметь отличительные черты, восприниматься иначе. Например, java.io.Serializable, java.lang.Cloneable, java.util.EventListener. Маркерные интерфейсы ещё известны как «теги» — они добавляют общий тег ко всем унаследованным классам и объединяют их в одну категорию.
При первом появлении в Java EE 5 аннотации были представлены как инструмент, который ускоряет разработку больших web-сервисов и клиентских приложений. Как это работает?
Обработка аннотации в Джава
На основе аннотаций компилятор может с помощью специальных обработчиков генерировать новый код и файлы конфигурации.
Обработчиками обычно выступают библиотеки и утилиты, которые можно брать у сторонних авторов (или создавать самостоятельно) и прикреплять к проекту в среде разработки. Способ подключения зависит от IDE или системы сборки. В Maven обработчики подключают с помощью модуля annotation-user или плагина maven-compiler-plugin.
Парсинг аннотаций происходит циклически. Компилятор ищет их в пользовательском коде и выбирает подходящие обработчики. Если вызванный обработчик на основе аннотации создаёт новые файлы с кодом, начинается следующий этап, где исходным материалом становится сгенерированный код. Так продолжается до тех пор, пока не будут созданы все необходимые файлы.
Пишем первую аннотацию на Java
Допустим, у нас есть веб-сервис, который поддерживает несколько версий одного функционала для соблюдения совместимости. И есть обработчик аннотаций, который позволяет компилятору выбирать нужные версии. На минутку забудем о существовании Git 🙂
Где хранить данные о версии и авторе функционала? Конечно же, в аннотации. Напишем её. Новую аннотацию объявляют с помощью ключевого слова @interface:
Это немного искусственный, но зато простой и наглядный пример аннотации на Java. Мы добавили два атрибута, которые выглядят как методы. Отличие в том, что при объявлении атрибутов никогда не используют оператор throws и не назначают параметров. Значениями могут выступать:
Можно указывать значения по умолчанию, что мы и сделали выше с полем author. При постановке аннотации атрибуты с дефолтными значениями можно пропускать.
Чтобы нашу аннотацию использовали только по назначению, вернёмся к её объявлению и укажем, где и когда она должна работать:
Остается ассоциировать аннотацию с нужным классом и запустить программу.
Используем аннотации Java для сравнения баз данных
Мы наметили конфигурацию работы с данными, остаётся её реализовать. Вот здесь на сцену выходят аннотации. С их помощью мы можем задать (а если потребуется — и скорректировать) способы обработки каждого поля. При этом конфигурация будет выглядеть абсолютно прозрачно: читатель кода сразу поймёт, что к чему применено. И ход исполнения программы не будет затронут. Волшебно! Пишем:
Теперь ассоциируем аннотацию с полем. Допустим, у нас в БД книги:
Присвоили id, присвоили имя полю в отчёте. Сверка значений со всеми базами запустится по умолчанию, т.к мы не указали для compareSources значение false. А если мы заведомо знаем, что искать нужно не во всех БД, а в конкретных? Например, сюжет есть не у любой книги. Задаём источники вручную:
Если у нас есть поле «Примечания», значение которого не нужно сверять, мы можем перед переменной для хранения примечаний поставить такую аннотацию:
Неплохо, но как сделать, чтобы правила обработки выбирались в зависимости от значения поля? Можно связать правила с классом обработчика, чтобы тот выбирал, как поступать с каждым полем. Для этого потребуется ещё одна короткая аннотация:
Проверим, как это работает. Давайте не сравнивать данные о литературе, которая поступила в библиотеку до 1990 года — допустим, эти данные у нас были только в одной базе:
Теперь реагировать на значения менее 1990 в поле «год поступления» мы доверяем конкретному пользовательскому классу. Это даёт нам больше гибкости в работе с данными, но помните, что аннотации — вещь статическая. Какое значение получил обработчик на входе, с тем и разбирается. Интерактива с динамической сменой значений пользователем не получится.
Как написать аннотацию на Java за 5 шагов
Разбираемся с аннотациями в Java: пошаговое руководство.
Аннотации нужны для описания метаданных — дополнительной информации о программных элементах. Они могут использоваться, например, для конфигурирования программного окружения или для выдачи указаний компилятору: чтобы он учитывал эти аннотации при проверке ошибок или подавлении предупреждений.
Аннотации — удобный способ собрать данные о сущности и правила их обработки в одном месте.
В Java есть встроенные аннотации. Например:
Но нам мало встроенных аннотаций. Поэтому научимся создавать свои: начнём с простой маркерной и за пять шагов дойдём до вершины мастерства — повторяющейся аннотации.
Текущий релиз Java SE с долгосрочной поддержкой (LTS) — Java 11. Поэтому описание синтаксиса и примеры ниже даны для этой версии языка.
Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.
Шаг нулевой, подготовительный
Формулируем задачу и описываем контекст
Потренируемся в написании аннотаций на примере Telegram-каналов: недавно мне попался очередной опрос от создателей мессенджера, в котором был и вопрос о числе подписок на каналы. Внезапно оказалось, что у меня их больше 30, самых разных, — вот пусть и поработают подопытными кроликами.
Создадим аннотации, показывающие, насколько интересен контент канала и какого рода информация там публикуется, ну а дальше как пойдёт 😀
Шаг первый
Создаём каркас аннотации
Аннотации похожи на интерфейсы. Если вы пока не очень ориентируетесь в интерфейсах, освежите свои знания о них в этой статье, перед тем как мы продолжим.
Они не просто похожи на интерфейсы: под капотом JVM (виртуальная машина Java) преобразует элементы аннотации в методы интерфейса, а саму аннотацию — в имплементацию (реализацию) этого интерфейса.
Вот как выглядит объявление простейшей аннотации для отметки особо интересных каналов:
Здесь, кроме заголовка, нет ничего полезного. Такие аннотации ещё называют маркерными — они действительно просто маркируют, обозначают какой-то признак.
При использовании маркерных аннотаций можно опускать круглые скобки после названия.
В аннотациях могут быть:
Шаг второй
Добавляем обязательные элементы
Напишем аннотацию, которой будем обозначать каналы о хобби. У неё будет один обязательный элемент — собственно описание хобби: фотография, музыка, путешествия и тому подобное.
После названия элемента обязательны круглые скобки — как для методов в интерфейсе. Набор типов для элементов ограничен, можно использовать только:
Это значит, что можно определить элемент таким образом:
Но нельзя, например, так:
final здесь недопустим, потому что конфликтует с неявным abstract. Это выглядит логичным, если вспомнить, что final запрещает переопределять метод, а abstract, напротив, требует реализации в наследниках. Исключение — только если наследник тоже абстрактный.
Каждому обязательному элементу необходимо задать значение — на то они и обязательные. Для определения элемента-массива указываются фигурные <> скобки. Если в массиве только один элемент (как во втором примере с музыкальным каналом), скобки можно опустить.
Где-то в коде вам может попасться аннотация, у которой в скобках просто написано значение элемента без названия, вот так:
Можете быть уверены, что у этой аннотации есть элемент с особым названием:
Необязательные элементы допустимы, но если захотите изменить их значения по умолчанию, придётся явно указать и название элемента value:
Шаг третий
Добавляем необязательные элементы
Сделаем необязательными все элементы, кроме kind. Для этого всем остальным элементам нужно задать значения по умолчанию.
Вот как это делается:
В качестве дефолтного значения может использоваться только константное НЕ null-значение. То есть вот такие объявления компилятор не пропустит:
Зато пустую строку в качестве значения по умолчанию использовать можно, вот так:
Текста по сравнению с предыдущим пунктом стало меньше, потому что значения необязательных элементов можно не указывать. В этом случае будут использоваться значения по умолчанию.
Однако никто не запрещает указать своё значение вместо дефолтного. В примере выше каналу NationalGeographicChannel мы установили не равный единице элемент level, а ещё задали для обоих классов непустые множества тегов — хоть это и необязательный элемент.
Кроме элементов со значениями по умолчанию, в аннотациях можно использовать константы. Они, как и в интерфейсах, неявно public, static и final. Вот, к примеру, аннотация для описания времени на чтение каналов, в которой заданы константы для минимального и максимального тайминга:
Шаг четвёртый
Определяем область действия
К чему применима аннотация
До этого шага мы использовали аннотации только для классов, но они применимы к интерфейсам, методам, параметрам класса, локальным переменным и не только. За область применимости аннотации отвечает другая аннотация — @Target. Полный список доступных значений для её единственного элемента value есть в официальной документации.
Если не задавать @Target, аннотацию можно использовать для любых программных элементов.
Мы же для примера напишем аннотацию, которая будет применима к методам и конструкторам классов. Предположим, она будет показывать, что после выполнения таких конструкторов и таких методов нужно будет отправить кому-то тревожное сообщение. Реализацию обработчика оставим за кадром, а аннотация будет выглядеть так:
Если теперь попробуем написать @Alarm перед названием самого класса SecureChannel, получим ошибку компиляции, потому что в @Target не включено значение для типа элемента «класс».
Когда аннотация доступна
Если мы и правда хотим прямо во время выполнения программы искать какие-то помеченные аннотацией @Alarm методы, одним только указанием @Target не обойтись.
Есть ещё одна аннотация для описания аннотаций — это @Retention. Она определяет доступность в течение жизненного цикла программы. У её единственного элемента value всего три доступных значения:
Воспользуемся новыми знаниями и допишем нашу тревожную аннотацию:
Теперь она будет доступна в рантайме, что и требовалось.
Как работают аннотации в Java и зачем они нужны
Java-аннотации используются для предоставления метаданных для вашего Java-кода. Будучи метаданными, напрямую не влияют на выполнение кода, хотя некоторые типы могут фактически использоваться для этой цели.
Обычно используются для следующих целей:
Java имеет 3 встроенных аннотации, которые вы можете использовать, чтобы давать инструкции компилятору.
Можно использовать во время сборки, когда вы создаете свой программный проект. Процесс включает в себя:
Сборка программного обеспечения обычно выполняется с помощью инструмента автоматической сборки, такого как Apache Ant или Apache Maven. Инструменты могут сканировать ваш код на предмет конкретных аннотаций и генерировать исходный код или другие файлы на основе их.
Доступ через отражение Java
Обычно аннотации отсутствуют в вашем коде после компиляции. Однако можно определить свои собственные. Затем к ним получить доступ через Java Reflection и использовать, чтобы давать инструкции для вашей программы или какого-либо стороннего API.
Основы
Символ @ сообщает компилятору, что это аннотация. Имя, следующее за символом @, является ее именем. В приведенном выше примере имя – Entity.
Элементы
Аннотация в этом примере содержит единственный элемент с именем tableName со значением, установленным для транспортных средств. Элементы заключены в круглые скобки после названия. Аннотации без элементов не нуждаются в скобках.
Размещение
Аннотация начинается с символа @, за которым следует имя. В этом случае имя – Entity.
Примечания не имеют конкретного значения в Java.
Встроенные
Java поставляется с тремя встроенными аннотациями, которые используются для предоставления инструкций компилятора Java:
@Deprecated
Использование @Deprecated над объявлением класса помечает класс как устаревший.
Вы также можете использовать @Deprecated выше для описания методов и полей, чтобы пометить метод или поле как устаревшие.
@Override
@Override используется над методами, которые переопределяют методы в суперклассе. Если метод не соответствует методу в суперклассе, компилятор выдаст вам ошибку.
@Override не обязательна для переопределения метода в суперклассе. В случае, если кто-то изменил имя переопределенного метода в суперклассе, ваш метод подкласса больше не будет переопределять его. Без @Override вы бы не узнали об этом. С @Override компилятор скажет вам, что метод в подклассе не переопределяет ни один метод в суперклассе.
В случае, если метод doTheThing() в MySuperClass изменяет подпись, так, что тот же метод в подклассе больше не переопределяет его, компилятор выдаст ошибку.
@SuppressWarnings
@SuppressWarnings заставляет компилятор подавлять предупреждения для данного метода. Например, если метод вызывает устаревший метод или выполняет небезопасное приведение типа, компилятор может сгенерировать предупреждение. Вы можете подавить эти предупреждения, пометив метод, содержащий код, @SuppressWarnings.
Создание собственных
Можно создавать свои собственные (пользовательские) аннотации. Определяются в своем собственном файле, как класс или интерфейс Java.
Пример
В этом примере определяется аннотация MyAnnotation, которая имеет четыре элемента. Обратите внимание на ключевое слово @interface. Это сигнализирует компилятору Java, что это определение примечаний.
Обратите внимание, что каждый элемент определяется аналогично определению метода в интерфейсе. У него есть тип данных и имя. Вы можете использовать все примитивные типы данных, а также массивы в качестве типа данных элемента. Сложные объекты в качестве типа данных не применяются.
Как видите, я должен указать значения для всех элементов аннотации MyAnnotation.
Значения элемента по умолчанию
Обратите внимание, что элемент значения больше не присутствует.
@Retention
Это то, что сигнализирует компилятору Java и JVM, что аннотация должна быть доступна через отражение во время выполнения.
Класс RetentionPolicy содержит еще два значения, которые вы можете использовать:
@Target
В этом примере показана аннотация, которую можно использовать только для аннотирования методов.
Класс ElementType содержит следующие возможные цели:
Большинство из них самоочевидны, но некоторые нет. Поэтому я объясню цели, которые не очевидны.
Цель ANNOTATION_TYPE означает определение. Таким образом, аннотация может использоваться только для аннотирования других примечаний. Как @Target и @Retention.
Цель TYPE означает любой тип. Тип – это класс, интерфейс, перечисление или аннотация.
@Inherited
В этом примере класс MySubClass наследует аннотацию @MyAnnotation, потому что MySubClass наследуется от MySuperClass, а MySuperClass имеет @MyAnnotation.
@Documented
При генерации JavaDoc для класса MySuperClass @MyAnnotation теперь включается в JavaDoc.
Аннотации в Java. Как создать свою аннотацию
Объясняю на пальцах, что такое аннотации в Java, а также рассказываю как создать свою аннотацию и обработчик к ней в Java.
Аннотации – это своеобразные маркеры, с помощью которых программист указывает компилятору Java и средствам разработки, что делать с участками кода помимо исполнения. Аннотировать можно переменные, параметры, классы, пакеты. Можно писать свои аннотации или использовать стандартные – встроенные в Java.
Обратите внимание, сама аннотация никак не влияет на переопределение метода, но позволяет контролировать успешность переопределения при компиляции или сборке. Мы защитили участок кода от неприметной ошибки, на поиск которой в большой программе ушли бы часы. Это лишь одно из многих применений аннотаций.
Структура аннотации
Параметры задаются как методы у интерфейсов, только без аргументов. А ключевое слово default — говорит про то, что метод по умолчанию будет возвращать определённое значение.
Так как мы не сконфигурировали аннотацию, то она может применяться к чему угодно: к классам, методам, атрибутам и т. п. Для того чтобы ограничить использование аннотации, её нужно разметить аннотациями 😄
Аннотация @Target позволяет ограничить область применения:
Если нужно, что бы ваша аннотация использовалась больше чем для одного типа, укажите @Target следующим образом:
Помимо @Target есть еще несколько аннотаций, для настройки:
@Retention определяет в каком жизненном цикле кода аннотация будет доступна.
@Inherited позволяет реализовать наследование аннотаций родительского класса классом-наследником
Обработчик аннотации
Но магии в программировании нет, и аннотации сами по себе ничего не делают, нужно написать обработчик аннотации.
А наша аннотация должна сгенерировать нам класс в том же пакете с названием SimpleFields :
Для этого создаем аннотацию @FieldNames :
Аннотация @SupportedAnnotationTypes отвечает за указание аннотации для которой этот обработчик создается.
Аннотация @AutoService упрощает создание манифеста. Но для нее нужно добавить новую зависимость
Дебажить обработчик обычным способом у вас не получится. Как дебажить обработчики я писал в отдельной статье: Дебаг приложения на этапе компиляции IntelliJ IDEA
Нам необходимо получить все классы, которые помечены нашей аннотацией.
Осталось написать логику обработки нашей аннотации. Логика будет такая:
Создадим 2 новых класса. ClassDto будет содержать информацию, необходимую для генерации нового класса. Класс FieldDto будет отвечать за информацию необходимую для создания public static final String полей. Лучше смотреть на примерах, так сложно объяснить.
Чтобы преобразовать имя переменной numberTwo в имя статической переменной NUMBER_TWO нам понадобиться еще одна зависисомость:
Теперь нам надо заполнить класс ClassDto информацией:
Рассмотрим класс генератор:
Проверка работы
Чтобы проверить работу нашей аннотации, создадим новый проект, в который добавим нашу библиотеку в виде зависимости.
Заключение
Этих базовых знаний будет достаточно, чтобы начать разбираться в создании собственных обработчиков для Java аннотаций.





