Обзор встроенных аннотаций Java
1. Overviewс
В этой статье мы поговорим об основной функции языка Java - аннотациях по умолчанию, доступных в JDK.
2. Что такое аннотация
Проще говоря, аннотации - этоJava types that are preceded by an “@” symbol.
У Java были аннотации начиная с версии 1.5. С тех пор они сформировали то, как мы разрабатываем наши приложения.
Spring и Hibernate являются отличными примерами фреймворков, которые сильно зависят от аннотаций для реализации различных методов проектирования.
В основномan annotation assigns extra metadata to the source code it’s bound to. Добавляя аннотацию к методу, интерфейсу, классу или полю, мы можем:
-
Сообщите компилятору о предупреждениях и ошибках
-
Манипулировать исходным кодом во время компиляции
-
Изменить или изучить поведение во время выполнения
3. Встроенные аннотации Java
Теперь, когда мы рассмотрели основы, давайте взглянем на некоторые аннотации, которые поставляются с ядром Java. Во-первых, есть несколько, которые сообщают компиляции:
-
@Override
-
@SuppressWarnings
-
@Deprecated
-
@SafeVarargs
-
@FunctionalInterface
Эти аннотации генерируют или подавляют предупреждения и ошибки компилятора. Последовательное их применение часто является хорошей практикой, поскольку их добавление может предотвратить будущие ошибки программиста.
Аннотация@Override используется, чтобы указать, что метод переопределяет или заменяет поведение унаследованного метода.
@SuppressWarnings указывает, что мы хотим игнорировать определенные предупреждения из части кода. Аннотация@SafeVarargs также действует на предупреждение, связанное с использованием varargs.
Аннотацию@Deprecated можно использовать, чтобы пометить API как не предназначенный для дальнейшего использования.
Для всего этого вы можете найти более подробную информацию в связанных статьях.
3.1. @FunctionalInterfaceс
Java 8 позволяет нам писать код более функционально.
Single Abstract Method interfaces - большая часть этого. If we intend a SAM interface to be used by lambdas, we can optionally mark it as such with @FunctionalInterface:
@FunctionalInterface
public interface Adder {
int add(int a, int b);
}
Подобно@Override с методами,@FunctionalInterface заявляет о наших намерениях с помощьюAdder.
Теперь, независимо от того, используем ли мы@FunctionalInterface или нет, мы все равно можем использоватьAdder таким же образом:
Adder adder = (a,b) -> a + b;
int result = adder.add(4,5);
Но если мы добавим второй метод кAdder,, компилятор пожалуется:
@FunctionalInterface
public interface Adder {
// compiler complains that the interface is not a SAM
int add(int a, int b);
int div(int a, int b);
}
Теперь это было бы скомпилировано без аннотации@FunctionalInterface. Итак, что это нам дает?
Как и@Override, эта аннотация защищает нас от будущих ошибок программиста. Even though it’s legal to have more than one method on an interface, it isn’t when that interface is being used as a lambda target. Без этой аннотации компилятор сломался бы в десятках мест, гдеAdder использовалось как лямбда. Теперьit just breaks in Adder itself.
4. Мета-аннотации
Далее, метааннотации - это аннотации, которые можно применять к другим аннотациям.
Например, эти метааннотации используются для конфигурации аннотации:
-
@Target
-
@Retention
-
@Inherited
-
@ Документированный
-
@Repeatable
4.1. @Targetс
Объем аннотаций может варьироваться в зависимости от требований. В то время как аннотация используется только с методами, другая аннотация может использоваться с объявлениями конструктора и полей.
Чтобы определить целевые элементы пользовательской аннотации, нам нужно пометить ее аннотацией@Target.
@Target может работать сeight different element types. Если мы посмотрим на исходный код @SafeVarargs, то увидим, что он должен быть прикреплен только к конструкторам или методам:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}
4.2. @Retentionс
Некоторые аннотации предназначены для использования в качестве подсказок для компилятора, в то время как другие используются во время выполнения.
We use the @Retention annotation to say where in our program’s lifecycle our annotation applies.
Для этого нам нужно настроить@Retention с одной из трех политик хранения:
-
RetentionPolicy.SOURCE – не виден ни компилятором, ни средой выполнения
-
RetentionPolicy.CLASS - виден компилятором
-
RetentionPolicy.RUNTIME –, видимый компилятором и средой выполнения
@Retention по умолчаниюRetentionPolicy.SOURCE.
Если у нас есть аннотация, которая должна быть доступна во время выполнения:
@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface RetentionAnnotation {
}
Затем, если мы добавим некоторые аннотации к классу:
@RetentionAnnotation
@Deprecated
public class AnnotatedClass {
}
Теперь мы можем поразмышлять надAnnotatedClass, чтобы увидеть, сколько аннотаций сохранено:
@Test
public void whenAnnotationRetentionPolicyRuntime_shouldAccess() {
AnnotatedClass anAnnotatedClass = new AnnotatedClass();
Annotation[] annotations = anAnnotatedClass.getClass().getAnnotations();
assertThat(annotations.length, is(1));
}
Значение равно 1, потому что @RetentionAnnotation имеет политику храненияRUNTIME, а@Deprecated - нет.
4.3. @Inheritedс
В некоторых ситуациях нам может понадобиться подкласс, чтобы аннотации были связаны с родительским классом.
Мы можем использовать аннотацию@Inherited, чтобы наша аннотация распространялась от аннотированного класса к его подклассам.
Если мы применим@Inherited к нашей пользовательской аннотации, а затем применим его кBaseClass:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {
}
@InheritedAnnotation
public class BaseClass {
}
public class DerivedClass extends BaseClass {
}
Затем, после расширения BaseClass, мы должны увидеть, чтоDerivedClass, похоже, имеет ту же аннотацию во время выполнения:
@Test
public void whenAnnotationInherited_thenShouldExist() {
DerivedClass derivedClass = new DerivedClass();
InheritedAnnotation annotation = derivedClass.getClass()
.getAnnotation(InheritedAnnotation.class);
assertThat(annotation, instanceOf(InheritedAnnotation.class));
}
Без аннотации@Inherited вышеуказанный тест не прошел бы.
4.4. @Documentedс
По умолчанию Java не документирует использование аннотаций в документации Javadocs.
But, we can use the @Documented annotation to change Java’s default behavior.
Если мы создадим настраиваемую аннотацию, которая использует@Documented:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
int value();
}
И примените его к соответствующему элементу Java:
public class Employee {
@ExcelCell(0)
public String name;
}
Затем JavadocEmployee покажет использование аннотации:
4.5. @Repeatableс
Иногда может быть полезно указывать одну и ту же аннотацию более одного раза для данного элемента Java.
До Java 7 нам приходилось группировать аннотации в одну аннотацию контейнера:
@Schedules({
@Schedule(time = "15:05"),
@Schedule(time = "23:00")
})
void scheduledAlarm() {
}
Тем не менее, Java 7 принесла более чистый подход. Withthe @Repeatable annotation,we can make an annotation repeatable:
@Repeatable(Schedules.class)
public @interface Schedule {
String time() default "09:00";
}
Чтобы использовать@Repeatable, нам также нужна аннотация контейнера. В этом случае мы повторно используем@Schedules:
public @interface Schedules {
Schedule[] value();
}
Конечно, это очень похоже на то, что было до Java 7. Но теперь значение состоит в том, что оболочка@Schedules больше не указывается, когда нам нужно повторить@Schedule:
@Schedule
@Schedule(time = "15:05")
@Schedule(time = "23:00")
void scheduledAlarm() {
}
Поскольку Java требует аннотации оболочки, нам было легко перейти от списков аннотаций до Java 7 к повторяющимся аннотациям.
5. Заключение
В этой статье мы говорили о встроенных аннотациях Java, с которыми должен быть знаком каждый разработчик Java.
Как всегда, все примеры статьи можно найтиover on GitHub.