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")
