Примечания

  • Статья написана на основе курса от компании 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.

@Deprecated

Помеченный этой аннотацией элемент устарел и больше не должен использоваться (это стоит отметить в Javadoc). При наличии такого элемента в программе компилятор сгенерирует предупреждение.

// Комментарий Javadoc:
/** 
* @deprecated 
* объяснение, почему метод устарел.
*/ 
@Deprecated 
static void deprecatedMethod() { }

@Override

Информирует компилятор о том, что аннотируемый элемент должен переопределять элемент родительского класса. При некорректном переопределении компилятор сгенерирует ошибку.

@Override
int overriddenMethod() { }

@SuppressWarnings

Подавляет генерируемые компилятором предупреждения.

Предупреждения делятся на непроверенные (unchecked) и устаревшие (deprecation). Первые возникают при использовании устаревшего кода, написанного до дженериков, вторые - при использовании кода, помеченного аннотацией @Deprecated.

Можно подавить как одну категорию, так и обе сразу:

@SuppressWarnings({"unchecked", "deprecation"})

@SafeVarargs

Применяется к методу или конструктору и утверждает, что код не выполняет потенциально небезопасных операций с параметрами 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

Используется при описании функционального интерфейса. Подчеркивает, что это именно функциональный интерфейс.

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

}

Аннотации, применимые к другим аннотациям (мета-аннотации)

Располагаются в пакете java.lang.annotation.

@Retention

Указывает, сколько хранится отмеченная аннотация.

  • RetentionPolicy.SOURCE. Отмеченная аннотация сохраняется только на уровне исходного кода и игнорируется компилятором.

  • RetentionPolicy.CLASS. Сохраняется компилятором во время компиляции, но игнорируется JVM.

  • RetentionPolicy.RUNTIME. Сохраняется JVM для использования во время выполнения программы.

@Documented

Указывает, что аннотация, должна быть задокументирована в Javadoc (по умолчанию аннотации не документируются).

@Target

Определяет права доступа аннотации (к каким элементам ее можно применять). В аннотации @Target указывается одно из следующих значений:

  • ElementType.ANNOTATION_TYPE. Применяется к аннотации

  • ElementType.CONSTRUCTOR. Применяется к конструктору.

  • ElementType.FIELD. Применяется к полю или свойству.

  • ElementType.LOCAL_VARIABLE. Применяется к локальной переменной.

  • ElementType.METHOD. Применяется к методу.

  • ElementType.PARAMETER. Применяется к параметру метода.

  • ElementType.TYPE. Применяется к любому элементу класса.

@Inherited

Аннотация будет наследоваться дочерним классом (по умолчанию аннотации не наследуются). Применима только к описаниям классов.

@Repeatable

Указывает, что аннотация повторяющаяся.

Повторяющиеся аннотации

Определение

Аннотации, которые могут применяться к одному и тому же элементу более одного раза.

Пример

Допустим, вам надо написать аннотацию, запускающую метод в заданное время или по определенному расписанию. В примере созданная аннотация @Schedule будет запускать метод каждый последний день месяца и каждую пятницу в 23:00.

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23") 
public void doPeriodicCleanup() { ... }

Создание повторяющейся аннотации

Для обеспечения обратной совместимости повторяющиеся аннотации хранятся в контейнере аннотаций, который автоматически генерируется java компилятором. Для генерации нужны следующие описания:

  1. Описание повторяющейся аннотации

Аннотация должна быть помечена @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)


  1. AotD
    20.04.2022 13:33

    Как абсолютный 0 в Java интересно - придумал ли кто-то аннотацию которая выкидывает из результирующего кода класс/метод при компиляции с определенными параметрами?

    Например нужно собирать несколько версий библиотеки из одной кодовой базы. Релизный для разработчиков и debug для тестировщиков. Для релизного нужно выкинуть методы манипулирования с сырыми данными.

    Разметить бы из каким-нибудь @DebugOnlyи вычищать в релизных сборках.


    1. Skynet2034
      20.04.2022 13:43
      +2

      Можно юзать разные Spring-профили. Дебажные методы вынести в отдельные бины и поставить им профиль, отличающийся от prod.


    1. Norgorn
      21.04.2022 08:42

      В java есть package-private методы, обычно их достаточно для таких случаев. Ну или унаследовать класс и сделать нужные методы публичными, некрасиво, но для тестов пойдёт. Или просто вызывать их с помощью рефлексии - тоже только для тестов вполне себе решение.


      1. Arty_Fact
        21.04.2022 13:30

        Нельзя унаследоваться от класса и «сделать нужные методы публичными». Дочерний класс не знает ничего о приватных методах родительского класса. То есть конечно можно в дочернем классе создать абсолютно такой же метод как в родительском, но это не будет переопределением. Можете поставить Override и убедиться.


        1. Norgorn
          21.04.2022 14:21

          Ну да, неоднозначно получилось, так просто нельзя, плохо написал.


  1. anaken
    21.04.2022 08:46

    Аннотации - это форма метаданных. Они предоставляют информацию о программе, при том сами частью программы не являются.

    Откуда такое определение пошло ? В любом случае, применительно к java - оно не верно. Аннотации в наше время используются все чаще, притом именно в ходе выполнения программы. Не зря для этого есть целый набор для работы с аннотациями.


    1. kacetal
      22.04.2022 23:08

      Ну от того что их часто используют, они не перестают быть метаданными.