Аннотации в Java. Кратко и по спецификации.

alex

Команда форума
Администратор
Итак, что же такое аннотации в Java?
Согласно спецификации, аннотация — это маркер, который связывает определенную информацию с программной конструкцией, но не оказывает прямого влияния на выполнение аннотируемого кода.

Какие программные конструкции могут быть аннотированы?
На самом деле, аннотацией может быть помечено практически всё, что угодно: пакет, класс (интерфейс, перечисление), конструктор, метод, поле класса, аргумент метода, локальная переменная в методе, объявление типа у типизированного класса, метода или конструктора, в выражениях extends, implements, instanceof, блоках try-with-resources, объявление типа исключения в выражениях catch, throws и даже сами классы аннотаций.

Причем объявление аннотации может быть помечено самой этой аннотацией — и это будет работать, циркулярность в отношении аннотаций допускается. То есть это вполне валидный код:
Java:
package ru.worldjb;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@WorldjbAnnotation
@Target(ElementType.TYPE)
public @interface WorldjbAnnotation {
}

Аннотации, которыми помечают объявления аннотаций, называются мета-аннотациями.
Кстати, собака (@) — это самостоятельный ключевой символ, его можно отделить, например, пробелом — будет работать, но так делать не рекомендуется, так как это нарушает code style, также как и отделение пробелом многоточия в varargs.

Класс аннотации представляет собой специальный вид интерфейса. Несмотря на то, что это явно не указывается, все аннотации являются наследниками интерфейсаjava.lang.annotation.Annotation и наследовать другие интерфейсы не могут. Поскольку это интерфейсы, то они могут иметь только абстрактные методы. Причем без аргументов.
Возвращаемым значением этих методов могут быть:
  • примитивы
  • String
  • Class
  • перечисление (enum)
  • аннотация
  • плоский (одномерный) массив элементов любого из вышеперечисленных типов
Возвращаемые значения передаются при использовании аннотации, внутри скобок в виде пар ключ = значение, перечисленных через запятую, где ключ — это имя метода аннотации, а значение — значение, соответствующее по типу возвращаемому значению этого метода.
Также в классе аннотации можно указать для методов значения по умолчанию, с помощью ключевого слова default после имени метода и скобок.
Соответственно, для тех методов, у которых имеется значение по умолчанию, при использовании аннотации передавать значение не обязательно.
Небольшой пример:
Java:
package ru.worldjb;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WorldjbAnnotation {
    int id();
    String[] strings() default "WorldJB"; // Значение по умолчанию.
    // В массив оборачивать не обязательно, компилятор сделает это за нас.
}
Java:
package ru.worldjb;

@WorldjbAnnotation(id = 1) // id передали, а strings — не обязательно
public class WorldJB {
    public static void main(String[] args) {
        System.out.println("Hello, WorldJB!");
    }
}

Есть 2 сокращённых формы использования аннотаций — одноэлементная и маркерная.
Если у аннотации один метод называется value, а все остальные (если они есть) имеют значения по умолчанию, то её можно использовать как одноэлементную. То есть, если мы хотим передать только значение для метода value, а для остальных (если они есть) нас устраивают значения по умолчанию, мы можем в скобках вместо value = значение просто указать значение.
Если же у аннотации нет методов или у всех методов есть значения по умолчанию — её можно использовать , как маркерную. То есть, если нас устраивают значения по умолчанию и мы не хотим передавать другие, то можно не ставить пустые скобки, а вообще опустить их.

5 встроенных мета-аннотаций Java:
1) @Retention - политика сохранения аннотации. Может принимать одно из 3 значений:
  • RetentionPolicy.SOURCE — аннотация сохраняется только в исходном коде и отбрасывается компилятором. То есть в байт-коде её не будет. Такие аннотации, как правило, обрабатываются AnnotationProcessor-ами — специальными классами, которые имплементируют интерфейс javax.annotation.processing.Processor и прописываются в файле META-INF/services/javax.annotation.processing.Processor внутри JAR. Компилятор проверяет все JAR-ы в classpath на наличие этого файла и вызывает все AnnotationProcessor-ы. Обычно это используется для автогенерации кода.
  • RetentionPolicy.CLASS — политика сохранения аннотаций по умолчанию — аннотация будет в байт-коде, но в рантайме через reflection она доступна не будет. Обычно используется для всяких AST трансформаций и прочих манипуляций с байт-кодом.
  • RetentionPolicy.RUNTIME — аннотация будет доступна в рантайме. Позволяет обрабатывать аннотации в рантайме. Используется в основном для dependency injection и подобных вещей.
2) @Target - массив типов элементов, которые могут быть аннотированы данной аннотацией (по умолчанию аннотацией может быть помечено всё, кроме объявления типа параметра в параметризованных классах, методах и конструкторах):
  • ElementType.PACKAGE - объявление пакета
  • ElementType.TYPE - объявление класса, интерфейса, перечисления или типа аннотации
  • ElementType.ANNOTATION_TYPE - объявление типа аннотации (полезно для мета-аннотаций)
  • ElementType.METHOD - объявление метода, включая методы аннотаций
  • ElementType.CONSTRUCTOR - объявление конструктора
  • ElementType.TYPE_PARAMETER - объявление параметров типов параметризованных классов, интерфейсов, методов и конструкторов
  • ElementType.FIELD - объявление поля класса
  • ElementType.PARAMETER - объявление формального параметра (параметра метода, конструктора) или параметра исключения (в блоке catch)
  • ElementType.LOCAL_VARIABLE - объявление локальной переменной в методе или конструкторе, в том числе в цикле for и блоке try-with-resources
  • ElementType.TYPE_USE - тип в выражениях extends и implements в объявлении класса; тип в выражении extends в объявлении интерфейса; тип возвращаемого значения метода (включая методы аннотаций); тип в выражении throws в объявлении метода или конструктора; тип в выражении extends в объявлении параметра типа типизированного класса, интерфейса, метода или конструктора; тип поля класса или интерфейса (включая enum константы); тип формального параметра метода, конструктора или лямбда-выражения; тип параметра получателя метода (для вложенных классов); тип локальной переменной; тип исключения (в catch, throws); тип в списке аргументов для вызова конструктора или выражения создания экземпляра класса или вызова метода; в выражении создания экземпляра класса, в качестве экземпляра класса, для которого создается экземпляр, или в качестве прямого суперкласса или прямого суперинтерфейса анонимного класса, для которого создается экземпляр; тип элемента в выражении создания массива; тип в операторе приведения типа; тип, который следует за оператором instanceof; в выражении ссылки на метод в качестве ссылочного типа для поиска метода-члена или в качестве типа класса или типа массива для построения.
3) @Repeatable - позволяет сделать аннотацию повторяемой, т.е. получить возможность несколько раз пометить аннотацией один и тот же элемент. Принимает класс аннотации-контейнера.
4) @Documented - информация об аннотации будет добавлена в Javadoc классов, где она использована.
5) @Inherited - даёт возможность наследования аннотации. Если класс помечен аннотацией, имеющей такую мета-аннотацию, то её унаследуют и дочерние классы. Что касается методов — если метод переопределяется, то его нужно аннотировать заново. По умолчанию аннотации не наследуются. Ввиду того, что в Java нет множественного наследования, аннотации над интерфейсами не наследуются, независимо от наличия мета-аннотации @Inherited.

Пожалуй, это вся основная информация по аннотациям в Java.
Материал написан на основе спецификации Java 8 (HTML | PDF).
 
Сверху Снизу