- Аннотации в Java, часть I
- Поехали!
- Ограничение: тип атрибута
- Ограничение: значения атрибутов — константы времени компиляции/загрузки JVM
- Заключение
- Контакты
- Аннотации в JAVA
- Применение
- Синтаксис
- Где в коде можно использовать аннотации
- Создание аннотации
- Синтаксис
- Пример
- Предопределенные аннотации
- Аннотации, использующиеся компилятором
- Аннотации, применимые к другим аннотациям (мета-аннотации)
- Повторяющиеся аннотации
- Определение
- Пример
- Создание повторяющейся аннотации
- Получение повторяющихся аннотаций
- Аннотации типов
- Определение
- Создание аннотации типов
- Применение
Аннотации в Java, часть I
Это первая часть статьи, посвященной такому языковому механизму Java 5+ как аннотации. Она имеет вводный характер и рассчитана на Junior разработчиков или тех, кто только приступает к изучению языка.
Я занимаюсь онлайн обучением Java и опубликую часть учебных материалов в рамках переработки курса Java Core.
Также я веду курс «Scala for Java Developers» на платформе для онлайн-образования udemy.com (аналог Coursera/EdX).
- строю усложняющуюся последовательность примеров
- объясняю возможные варианты применения
- объясняю логику двигавшую авторами (по мере возможности)
- даю большое количество тестов (50-100) всесторонне проверяющее понимание и демонстрирующих различные комбинации
- даю лабораторные для самостоятельной работы
Поехали!
Учебный пример: снабдить классы пользователя мета-информацией о «версии класса».
Итерация #1:
Просто ставим @ перед interface.
Итерация #2:
У аннотаций могут быть атрибуты.
@Version(version = 42) public class MyClass <>
Аннотация выше полностью эквивалентна следующей (без public). В этом аннотации схожи с интерфейсам: отсутствие модификатора области видимости автоматически означает public (а не package private как у классов).
public @interface Version < protected int version(); >>> COMPILATION ERROR: Modifier 'protected' not allowed here
Далее я буду использовать вариант без модификатора public
Итерация #3:
Если объявить атрибут с именем value, то его можно опускать при использовании
@Version(42) public class MyClass <>
@Version(value = 42) public class MyClass <>
Итерация #4:
Для атрибута можно объявить значения по умолчанию
@Version(42) public class MyClass <>
@Version(value = 42, author = "Jim Smith") public class MyClass
@Version(42, author = "Jim Smith") public class MyClass <> >> COMPILATION ERROR: Annotation attribute must be of the form 'name=value'
Итерация #5:
Атрибуты могут иметь тип массива
@Author() public class MyClass <>
public @interface Author2D < String[][] value() default <>; > >> COMPILATION ERROR: Invalid type of annotation member
Итерация #6:
Возможен забавный трюк: аннотация — атрибут аннотации
@History(< @Version(1), @Version(value = 2, author = "Jim Smith") >) public class MyClass <>
У аннотаций много ограничений. Перечислим некоторые из них.
Ограничение: тип атрибута
- примитивы
- String
- Class или «any parameterized invocation of Class»
- enum
- annotation
- массив элементов любого из вышеперечисленных типов
Ну что же, давайте действовать в рамках ограничений
Итерация #7:
В качестве типа атрибута нельзя использовать «обычные» классы Java (за исключением java.lang.String и java.lang.Class), скажем java.util.Date
import java.util.Date; public @interface Version < Date date(); >>> COMPILATION ERROR: Invalid type for annotation member
@Date(year = 2001, month = 1, day = 1) public class MyClass <>
Итерация #8:
Атрибутом аннотации может быть enum. Из приятного, его можно объявить в объявлении аннотации (как и в объявлении интерфейса тут может быть объявление enum, class, interface, annotation)
public @interface Colored < public enum Color Color value(); >
import static net.golovach.Colored.Color.RED; @Colored(RED) public class MyClass <>
Итерация #9:
Атрибутом аннотации может быть классовый литерал.
Аннотация версии включает ссылку на предыдущую версию класса.
public @interface Version < int value(); Classprevious() default Void.class; >
@Version(1) public class ClassVer1 <>
@Version(value = 2, previous = ClassVer1.class) public class ClassVer2 <>
// Да, я знаю, что нормальные люди не включают версию класса в имя класса. Но знаете как нудно придумывать примеры согласованные с реальной практикой?
Итерация #10:
Менее тривиальный пример с классовым литералом, где я не удержался и добавил generic-ов.
Интерфейс «сериализатора» — того, кто может записать экземпляр T в байтовый поток вывода
import java.io.IOException; import java.io.OutputStream; public interface Serializer
import java.io.IOException; import java.io.OutputStream; public class MySerializer implements Serializer < @Override public void toStream(MyClass obj, OutputStream out) throws IOException < throw new UnsupportedOperationException(); >>
public @interface SerializedBy < Classvalue(); >
@SerializedBy(MySerializer.class) public class MyClass <>
Итерация #11:
Сложный пример
public @interface Version < int version(); Date date(); Author[] authors() default <>; Class >previous() default Void.class; >
import static net.golovach.JobTitle.*; @History(< @Version( version = 1, date = @Date(year = 2001, month = 1, day = 1)), @Version( version = 2, date = @Date(year = 2002, month = 2, day = 2), authors = <@Author(value = "Jim Smith", title = JUNIOR)>, previous = MyClassVer1.class), @Version( version = 3, date = @Date(year = 2003, month = 3, day = 3), authors = < @Author(value = "Jim Smith", title = MIDDLE), @Author(value = "Anna Lea")>, previous = MyClassVer2.class) >) public class MyClassVer3 <>
Ограничение: значения атрибутов — константы времени компиляции/загрузки JVM
Должна быть возможность вычислить значения атрибутов аннотаций в момент компиляции или загрузки класса в JVM.
public @interface SomeAnnotation
@SomeAnnotation( count = 1 + 2, name = MyClass.STR + "Hello" ) public class MyClass
@SomeAnnotation( count = (int) Math.PI, name = "" + Math.PI ) public class MyClass <>
@SomeAnnotation( count = (int) Math.sin(1), name = "Hello!".toUpperCase() ) public class MyClass <> >> COMPILATION ERROR: Attribute value must be constant
Заключение
- Аннотации, модифицирующие поведение других аннотаций: @ Target, @ Retention, @ Documented, @ Inherited
- Аннотации, модифицирующие поведение компилятора и JVM: @ Deprecated, @ Override, @ SafeVarargs, @ SuppressWarnings
- Чтение аннотаций с помощью Reflection API
Контакты
Я занимаюсь онлайн обучением Java (вот курсы программирования) и публикую часть учебных материалов в рамках переработки курса Java Core. Видеозаписи лекций в аудитории Вы можете увидеть на youtube-канале, возможно, видео канала лучше систематизировано в этой статье.
skype: GolovachCourses
email: GolovachCourses@gmail.com
Аннотации в JAVA
Аннотации — это форма метаданных. Они предоставляют информацию о программе, при том сами частью программы не являются.
Применение
- Информация для компилятора. Могут использоваться компилятором для обнаружения ошибок и подавления предупреждений.
- Обработка во время компиляции и развертывания. Программа может создавать код, 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
Где в коде можно использовать аннотации
Аннотации применяются с объявлениями классов, полей и других элементов программы.
Аннотации, использующиеся с типами, называются аннотациями типов. Примеры таких аннотаций:
myString = (@NonNull String) str;
class UnmodifiableList implements @Readonly List
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 >
Описание аннотации, которая заменит комментарии:
Использование созданной аннотации:
@ClassPreamble ( author = "John Doe", date = "3/17/2002", currentRevision = 6, lastModified = "4/12/2004", lastModifiedBy = "Jane Doe", reviewers = ) 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.
Можно подавить как одну категорию, так и обе сразу:
Применяется к методу или конструктору и утверждает, что код не выполняет потенциально небезопасных операций с параметрами varargs. При использовании аннотации подавляются unchecked предупреждения, связанные с varargs.
@SafeVarargs // На самом деле не безопасно! static void m(List. stringLists) < Object[] array = stringLists; ListtmpList = Arrays.asList(42); array[0] = tmpList; //Написано неверно, но скомпилируется без предупреждения String s = stringLists[0].get(0); //ClassCastException >
Используется при описании функционального интерфейса. Подчеркивает, что это именно функциональный интерфейс.
@FunctionalInterface public interface Predicate
Аннотации, применимые к другим аннотациям (мета-аннотации)
Располагаются в пакете 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
Контейнер должен содержать массив повторяющихся аннотаций.
public @interface Schedules
Получение повторяющихся аннотаций
Reflection API предоставляет методы для получения аннотаций. При получении повторяющихся аннотаций поведение методов, которые возвращают одну аннотацию (например, AnnotatedElement.getAnnotation(Class) ) не меняется. Если нужно вернуть более одной, то необходимо сначала получить контейнер. Таким образом устаревший код продолжает работать. Также, для получения повторяющихся аннотаций можно использовать методы Java SE 8 (‘AnnotatedElement.getAnnotationsByType(Class)’).
Для получения информации о всех методах, обратитесь к документации класса AnnotatedElement.
Аннотации типов
Определение
Аннотации типов — аннотации, которые применяются вместе с типами. Везде, где вы видите тип, можно использовать эту аннотацию. Например, с оператором new, при приведении, при имплементации и при использовании throws.
Создание аннотации типов
Для создания аннотации типов в @Target указываются следующие значения, либо одно из них:
@Target () public @interface Test
TYPE_PARAMETER , означает, что аннотацию можно применять к переменным типа (например, MyClass ). TYPE_USE , разрешает применение с любыми типами.
Применение
Аннотации типов предназначены для улучшенного анализа программ и более строгой проверки типов. Например, @NonNull String str; . Java SE8 определяет аннотации типов, но не реализует. Вместо этого предлагается использовать сторонние фреймворки, реализующие их (Checker Framework).