Java Language
Аннотации
Поиск…
Вступление
В Java аннотация - это форма синтаксических метаданных, которые могут быть добавлены в исходный код Java. Он предоставляет данные о программе, которая не является частью самой программы. Аннотации не оказывают прямого влияния на работу кода, который они комментируют. Классы, методы, переменные, параметры и пакеты могут быть аннотированы.
Синтаксис
- @AnnotationName // «Аннотирование маркера» (без параметров)
- @AnnotationName (someValue) // устанавливает параметр с именем 'value'
- @AnnotationName (param1 = value1) // named parameter
- @AnnotationName (param1 = value1, param2 = value2) // несколько именованных параметров
- @AnnotationName (param1 = {1, 2, 3}) // параметр имени array
- @AnnotationName ({value1}) // массив с одним элементом в качестве параметра с именем 'value'
замечания
Типы параметров
Для параметров могут использоваться только постоянные выражения следующих типов, а также массивы этих типов:
-
String
-
Class
- примитивные типы
- Типы перечислений
- Типы аннотаций
Встроенные аннотации
Стандартная версия Java содержит предопределенные аннотации. Вам не нужно определять их самостоятельно, и вы можете использовать их немедленно. Они позволяют компилятору разрешить некоторую фундаментальную проверку методов, классов и кода.
@Override
Эта аннотация относится к методу и говорит, что этот метод должен переопределять метод суперкласса или реализовать определение метода абстрактного суперкласса. Если эта аннотация используется с любым другим способом, компилятор выдает ошибку.
Бетонный суперкласс
public class Vehicle {
public void drive() {
System.out.println("I am driving");
}
}
class Car extends Vehicle {
// Fine
@Override
public void drive() {
System.out.prinln("Brrrm, brrm");
}
}
Абстрактный класс
abstract class Animal {
public abstract void makeNoise();
}
class Dog extends Animal {
// Fine
@Override
public void makeNoise() {
System.out.prinln("Woof");
}
}
Не работает
class Logger1 {
public void log(String logString) {
System.out.prinln(logString);
}
}
class Logger2 {
// This will throw compile-time error. Logger2 is not a subclass of Logger1.
// log method is not overriding anything
@Override
public void log(String logString) {
System.out.println("Log 2" + logString);
}
}
Основная цель - уловить туманность, где вы думаете, что вы переопределяете метод, но на самом деле определяете новый.
class Vehicle {
public void drive() {
System.out.println("I am driving");
}
}
class Car extends Vehicle {
// Compiler error. "dirve" is not the correct method name to override.
@Override
public void dirve() {
System.out.prinln("Brrrm, brrm");
}
}
Обратите внимание, что значение @Override
со временем изменилось:
- В Java 5 это означало, что аннотированный метод должен был переопределить не абстрактный метод, объявленный в цепочке суперкласса.
- Начиная с Java 6, он также выполняется, если аннотированный метод реализует абстрактный метод, объявленный в иерархии классов суперклассов / интерфейсов.
(Иногда это может вызвать проблемы при обратном переносе кода на Java 5.)
@Deprecated
Это означает, что метод устарел. Это может быть несколько причин:
API является ошибочным и нецелесообразно исправлять,
использование API, вероятно, приведет к ошибкам,
API был заменен другим API,
API устарел,
API является экспериментальным и подлежит несовместимым изменениям,
или любую комбинацию вышеуказанного.
Конкретную причину устаревания обычно можно найти в документации API.
Аннотации заставят компилятор испускать ошибку, если вы ее используете. IDE также могут выделить этот метод как-то как устаревший
class ComplexAlgorithm {
@Deprecated
public void oldSlowUnthreadSafeMethod() {
// stuff here
}
public void quickThreadSafeMethod() {
// client code should use this instead
}
}
@SuppressWarnings
Почти во всех случаях, когда компилятор выдает предупреждение, наиболее подходящим действием является устранение причины. В некоторых случаях (например, код Generics, использующий, например, нестандартный код для предварительного генерирования) это может быть невозможно, и лучше запретить эти предупреждения, которые вы ожидаете и не можете исправить, чтобы вы могли более четко видеть неожиданные предупреждения.
Эта аннотация может применяться ко всему классу, методу или строке. В качестве параметра используется категория предупреждения.
@SuppressWarnings("deprecation")
public class RiddledWithWarnings {
// several methods calling deprecated code here
}
@SuppressWarning("finally")
public boolean checkData() {
// method calling return from within finally block
}
Лучше максимально ограничить объем аннотации, чтобы предотвратить непредвиденные предупреждения. Например, ограничение объема аннотации на одну строку:
ComplexAlgorithm algorithm = new ComplexAlgorithm();
@SuppressWarnings("deprecation") algoritm.slowUnthreadSafeMethod();
// we marked this method deprecated in an example above
@SuppressWarnings("unsafe") List<Integer> list = getUntypeSafeList();
// old library returns, non-generic List containing only integers
Предупреждения, поддерживаемые этой аннотацией, могут отличаться от компилятора к компилятору. Только unchecked
и deprecation
предупреждения упомянуты в JLS. Непризнанные типы предупреждений будут игнорироваться.
@SafeVarargs
Из-за стирания типа void method(T... t)
будет преобразован в void method(Object[] t)
что означает, что компилятор не всегда может проверить, что использование varargs является безопасным по типу. Например:
private static <T> void generatesVarargsWarning(T... lists) {
Существуют случаи, когда использование безопасно, и в этом случае вы можете аннотировать метод с SafeVarargs
аннотации SafeVarargs
для подавления предупреждения. Это явно скрывает предупреждение, если ваше использование также небезопасно.
@FunctionalInterface
Это необязательная аннотация, используемая для обозначения FunctionalInterface. Это заставит компилятор жаловаться, если он не соответствует спецификации FunctionalInterface (имеет один абстрактный метод)
@FunctionalInterface
public interface ITrade {
public boolean check(Trade t);
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Проверка аннотации выполнения через отражение
API Reflection Java позволяет программисту выполнять различные проверки и операции над полями, методами и аннотациями классов во время выполнения. Однако для того, чтобы аннотация была видимой во время выполнения, RetentionPolicy
необходимо изменить на RUNTIME
, как показано в следующем примере:
@interface MyDefaultAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyRuntimeVisibleAnnotation {
}
public class AnnotationAtRuntimeTest {
@MyDefaultAnnotation
static class RuntimeCheck1 {
}
@MyRuntimeVisibleAnnotation
static class RuntimeCheck2 {
}
public static void main(String[] args) {
Annotation[] annotationsByType = RuntimeCheck1.class.getAnnotations();
Annotation[] annotationsByType2 = RuntimeCheck2.class.getAnnotations();
System.out.println("default retention: " + Arrays.toString(annotationsByType));
System.out.println("runtime retention: " + Arrays.toString(annotationsByType2));
}
}
Определение типов аннотаций
Типы аннотаций определяются с помощью @interface
. Параметры определяются аналогично методам регулярного интерфейса.
@interface MyAnnotation {
String param1();
boolean param2();
int[] param3(); // array parameter
}
Значения по умолчанию
@interface MyAnnotation {
String param1() default "someValue";
boolean param2() default true;
int[] param3() default {};
}
Мета-аннотаций
Мета-аннотации - это аннотации, которые могут применяться к типам аннотаций. Специальная предопределенная мета-аннотация определяет, как можно использовать типы аннотаций.
@Target
Мета-аннотация @Target
ограничивает типы, к которым может применяться аннотация.
@Target(ElementType.METHOD)
@interface MyAnnotation {
// this annotation can only be applied to methods
}
Несколько значений могут быть добавлены с использованием нотации массива, например @Target({ElementType.FIELD, ElementType.TYPE})
Доступные значения
ElementType | цель | пример использования целевого элемента |
---|---|---|
ANNOTATION_TYPE | типы аннотаций | |
КОНСТРУКТОР | конструкторы | |
Область | поля, константы перечисления | |
LOCAL_VARIABLE | объявления переменных внутри методов | |
ПАКЕТ | пакет (в package-info.java ) | |
МЕТОД | методы | |
ПАРАМЕТР | параметры метода / конструктора | |
ТИП | классы, интерфейсы, перечисления | |
ElementType | цель | пример использования целевого элемента |
---|---|---|
TYPE_PARAMETER | Объявление параметров типа | |
TYPE_USE | Использование типа | |
@Retention
Мета-аннотация @Retention
определяет видимость аннотации во время процесса или выполнения компиляции приложений. По умолчанию аннотации включены в .class
файлы, но не отображаются во время выполнения. Чтобы сделать аннотацию доступной во время выполнения, в этой аннотации необходимо установить RetentionPolicy.RUNTIME
.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
// this annotation can be accessed with reflections at runtime
}
Доступные значения
Политика удержания | эффект |
---|---|
УЧЕБНЫЙ КЛАСС | Аннотации доступны в файле .class , но не во время выполнения |
RUNTIME | Аннотации доступны во время выполнения и могут быть доступны посредством отражения |
ИСТОЧНИК | Аннотации доступны во время компиляции, но не добавляются в .class . Аннотацию можно использовать, например, обработчиком аннотации. |
@Documented
Мета-аннотация @Documented
используется для обозначения аннотаций, использование которых должно быть документировано генераторами документации API, такими как javadoc . Он не имеет значений. С помощью @Documented
все классы, использующие аннотацию, будут перечислены на их сгенерированной странице документации. Без @Documented
невозможно увидеть, какие классы используют аннотацию в документации.
@Inherited
@Inherited
метаинформация @Inherited
имеет отношение к аннотациям, которые применяются к классам. Он не имеет значений. Пометка аннотации как @Inherited
изменяет способ обработки аннотаций.
- Для не унаследованной аннотации запрос рассматривает только исследуемый класс.
- Для унаследованной аннотации запрос также проверяет цепочку суперкласса (рекурсивно) до тех пор, пока не будет найден экземпляр аннотации.
Обратите внимание, что запрашиваются только суперклассы: любые аннотации, привязанные к интерфейсам в иерархии классов, будут игнорироваться.
@Repeatable
@Repeatable
мета-аннотация @Repeatable
в Java 8. Она указывает, что к @Repeatable
аннотации можно добавить несколько экземпляров аннотации. Эта мета-аннотация не имеет значений.
Получение значений аннотации во время выполнения
Вы можете получить текущие свойства аннотации, используя Reflection, чтобы получить метод или поле или класс, к которым применена аннотация, и затем выбор желаемых свойств.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String key() default "foo";
String value() default "bar";
}
class AnnotationExample {
// Put the Annotation on the method, but leave the defaults
@MyAnnotation
public void testDefaults() throws Exception {
// Using reflection, get the public method "testDefaults", which is this method with no args
Method method = AnnotationExample.class.getMethod("testDefaults", null);
// Fetch the Annotation that is of type MyAnnotation from the Method
MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
// Print out the settings of the Annotation
print(annotation);
}
//Put the Annotation on the method, but override the settings
@MyAnnotation(key="baz", value="buzz")
public void testValues() throws Exception {
// Using reflection, get the public method "testValues", which is this method with no args
Method method = AnnotationExample.class.getMethod("testValues", null);
// Fetch the Annotation that is of type MyAnnotation from the Method
MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
// Print out the settings of the Annotation
print(annotation);
}
public void print(MyAnnotation annotation) {
// Fetch the MyAnnotation 'key' & 'value' properties, and print them out
System.out.println(annotation.key() + " = " + annotation.value());
}
public static void main(String[] args) {
AnnotationExample example = new AnnotationExample();
try {
example.testDefaults();
example.testValues();
} catch( Exception e ) {
// Shouldn't throw any Exceptions
System.err.println("Exception [" + e.getClass().getName() + "] - " + e.getMessage());
e.printStackTrace(System.err);
}
}
}
Выход будет
foo = bar
baz = buzz
Повторяющиеся аннотации
До Java 8 два экземпляра одной аннотации не могли быть применены к одному элементу. Стандартное обходное решение заключалось в использовании аннотации контейнера, содержащей массив некоторой другой аннотации:
// Author.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String value();
}
// Authors.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Authors {
Author[] value();
}
// Test.java
@Authors({
@Author("Mary"),
@Author("Sam")
})
public class Test {
public static void main(String[] args) {
Author[] authors = Test.class.getAnnotation(Authors.class).value();
for (Author author : authors) {
System.out.println(author.value());
// Output:
// Mary
// Sam
}
}
}
Java 8 обеспечивает более чистый, более прозрачный способ использования аннотаций контейнеров, используя аннотацию @Repeatable
. Сначала добавим это в класс Author
:
@Repeatable(Authors.class)
Это говорит Java обрабатывать несколько аннотаций @Author
как если бы они были окружены контейнером @Authors
. Мы также можем использовать Class.getAnnotationsByType()
для доступа к массиву @Author
своим собственным классом, а не через его контейнер:
@Author("Mary")
@Author("Sam")
public class Test {
public static void main(String[] args) {
Author[] authors = Test.class.getAnnotationsByType(Author.class);
for (Author author : authors) {
System.out.println(author.value());
// Output:
// Mary
// Sam
}
}
}
Унаследованные аннотации
По умолчанию аннотации классов не применяются к типам, расширяющим их. Это можно изменить, добавив аннотацию @Inherited
в определение аннотации
пример
Рассмотрим следующие 2 аннотации:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotationType {
}
а также
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UninheritedAnnotationType {
}
Если три класса аннотируются следующим образом:
@UninheritedAnnotationType
class A {
}
@InheritedAnnotationType
class B extends A {
}
class C extends B {
}
запуск этого кода
System.out.println(new A().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println(new B().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println(new C().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println("_________________________________");
System.out.println(new A().getClass().getAnnotation(UninheritedAnnotationType.class));
System.out.println(new B().getClass().getAnnotation(UninheritedAnnotationType.class));
System.out.println(new C().getClass().getAnnotation(UninheritedAnnotationType.class));
напечатает результат, подобный этому (в зависимости от пакетов аннотации):
null
@InheritedAnnotationType()
@InheritedAnnotationType()
_________________________________
@UninheritedAnnotationType()
null
null
Обратите внимание, что аннотации могут наследоваться только от классов, а не от интерфейсов.
Обработка времени компиляции с использованием обработчика аннотаций
В этом примере показано, как выполнить проверку времени компиляции аннотированного элемента.
Аннотации
@Setter
- это маркер, который можно применить к методам. Аннотации будут отброшены во время компиляции, которые впоследствии не будут доступны.
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Setter {
}
Обработчик аннотации
Класс SetterProcessor
используется компилятором для обработки аннотаций. Он проверяет, если методы аннотированные с @Setter
аннотаций являются public
, неправительственные static
методы с именем , начинающимся с set
и имеющий заглавную букву как 4 буквы. Если одно из этих условий не выполняется, в Messager
записывается ошибка. Компилятор записывает это в stderr, но другие инструменты могут использовать эту информацию по-разному. Например, IDE NetBeans позволяет пользователю задавать обработчики аннотаций, которые используются для отображения сообщений об ошибках в редакторе.
package annotation.processor;
import annotation.Setter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"annotation.Setter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {
private Messager messager;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// get elements annotated with the @Setter annotation
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Setter.class);
for (Element element : annotatedElements) {
if (element.getKind() == ElementKind.METHOD) {
// only handle methods as targets
checkMethod((ExecutableElement) element);
}
}
// don't claim annotations to allow other processors to process them
return false;
}
private void checkMethod(ExecutableElement method) {
// check for valid name
String name = method.getSimpleName().toString();
if (!name.startsWith("set")) {
printError(method, "setter name must start with \"set\"");
} else if (name.length() == 3) {
printError(method, "the method name must contain more than just \"set\"");
} else if (Character.isLowerCase(name.charAt(3))) {
if (method.getParameters().size() != 1) {
printError(method, "character following \"set\" must be upper case");
}
}
// check, if setter is public
if (!method.getModifiers().contains(Modifier.PUBLIC)) {
printError(method, "setter must be public");
}
// check, if method is static
if (method.getModifiers().contains(Modifier.STATIC)) {
printError(method, "setter must not be static");
}
}
private void printError(Element element, String message) {
messager.printMessage(Diagnostic.Kind.ERROR, message, element);
}
@Override
public void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// get messager for printing errors
messager = processingEnvironment.getMessager();
}
}
упаковка
Для применения компилятором процессор обработки аннотаций должен быть доступен для SPI (см. ServiceLoader ).
Для этого нужно добавить текстовый файл META-INF/services/javax.annotation.processing.Processor
необходимо добавить в файл jar, содержащий процессор аннотации, и аннотацию в дополнение к другим файлам. В файле должно быть указано полное имя обработчика аннотаций, то есть оно должно выглядеть так:
annotation.processor.SetterProcessor
Мы предположим, что файл jar называется AnnotationProcessor.jar
ниже.
Пример аннотированного класса
Следующий класс является примером класса в пакете по умолчанию с аннотациями, которые применяются к правильным элементам в соответствии с политикой хранения. Однако только обработчик аннотации рассматривает второй метод как действительную цель аннотации.
import annotation.Setter;
public class AnnotationProcessorTest {
@Setter
private void setValue(String value) {}
@Setter
public void setString(String value) {}
@Setter
public static void main(String[] args) {}
}
Использование обработчика аннотации с javac
Если обработчик аннотации обнаружен с использованием SPI, он автоматически используется для обработки аннотированных элементов. Например, компиляция класса AnnotationProcessorTest
с использованием
javac -cp AnnotationProcessor.jar AnnotationProcessorTest.java
дает следующий результат
AnnotationProcessorTest.java:6: error: setter must be public
private void setValue(String value) {}
^
AnnotationProcessorTest.java:12: error: setter name must start with "set"
public static void main(String[] args) {}
^
2 errors
вместо компиляции в обычном режиме. Файл .class
не создается.
Этого можно предотвратить, указав параметр -proc:none
для javac
. Вы также можете отказаться от обычной компиляции, указав -proc:only
вместо этого.
Интеграция IDE
Netbeans
Обработчики аннотаций могут использоваться в редакторе NetBeans. Для этого в настройках проекта необходимо указать процессор аннотации:
перейдите в «
Project Properties
> «Build
> «Compiling
добавить флажки для
Enable Annotation Processing
иEnable Annotation Processing in Editor
нажмите «
Add
рядом с списком процессоров аннотацийв появившемся всплывающем окне введите полное имя класса обработчика аннотации и нажмите «
Ok
.
Результат
Идея аннотаций
Спецификация языка Java описывает аннотации следующим образом:
Аннотирование - это маркер, который связывает информацию с конструкцией программы, но не влияет на время выполнения.
Аннотации могут отображаться перед типами или объявлениями. Они могут появляться в месте, где они могут применяться как к типу, так и к объявлению.
То, к чему относится аннотация, регулируется «мета-аннотацией» @Target
. Дополнительную информацию см. В разделе «Определение типов аннотаций» .
Аннотации используются для множества целей. Структуры, такие как Spring и Spring-MVC, используют аннотации для определения того, где должны быть введены зависимости или где должны быть маршрутизированы запросы.
Другие фреймворки используют аннотации для генерации кода. Ломбок и JPA - яркие примеры, которые используют аннотации для генерации кода Java (и SQL).
Цель этой темы - предоставить полный обзор:
Как определить свои собственные аннотации?
Какие аннотации предоставляет Java-язык?
Как используются аннотации на практике?
Аннотации для параметров «этого» и приемника
Когда впервые были введены аннотации Java, не было никаких условий для аннотирования цели метода экземпляра или параметра скрытого конструктора для конструктора внутренних классов. Это было исправлено на Java 8 с добавлением объявлений параметров приемника ; см. JLS 8.4.1 .
Параметр получателя является необязательным синтаксическим устройством для метода экземпляра или конструктора внутреннего класса. Для метода экземпляра параметр приемника представляет объект, для которого вызывается метод. Для конструктора внутреннего класса параметр-приемник представляет собой немедленно включающий экземпляр вновь созданного объекта. В любом случае параметр приемника существует только для того, чтобы разрешить тип отображаемого объекта в исходном коде, чтобы тип мог быть аннотирован. Параметр приемника не является формальным параметром; точнее, это не объявление какой-либо переменной (§4.12.3), оно никогда не связано ни с каким значением, переданным в качестве аргумента в выражении вызова метода или в выражении для создания экземпляра класса, и оно не оказывает никакого влияния на время выполнения.
Следующий пример иллюстрирует синтаксис для обоих типов параметров приемника:
public class Outer {
public class Inner {
public Inner (Outer this) {
// ...
}
public void doIt(Inner this) {
// ...
}
}
}
Единственная цель параметров приемника - дать вам возможность добавлять аннотации. Например, у вас может быть пользовательская аннотация @IsOpen
, целью которой является утверждение, что объект Closeable
не был закрыт при вызове метода. Например:
public class MyResource extends Closeable {
public void update(@IsOpen MyResource this, int value) {
// ...
}
public void close() {
// ...
}
}
На одном уровне аннотация @IsOpen
на this
может просто служить документацией. Однако мы могли бы сделать больше. Например:
- Обработчик аннотации может вставить проверку времени выполнения, что
this
не в закрытом состоянии при вызовеupdate
. - Средство проверки кода может выполнять статический анализ кода, чтобы найти случаи, когда
this
может быть закрыто при вызовеupdate
.
Добавить несколько значений аннотации
Параметр Annotation может принимать несколько значений, если он определен как массив. Например, стандартная аннотация @SuppressWarnings
определяется следующим образом:
public @interface SuppressWarnings {
String[] value();
}
Параметр value
представляет собой массив строк. Вы можете установить несколько значений, используя нотацию, похожую на инициализаторы массива:
@SuppressWarnings({"unused"})
@SuppressWarnings({"unused", "javadoc"})
Если вам нужно только установить одно значение, скобки можно опустить:
@SuppressWarnings("unused")