1. Обзор
В этой статье мы поговорим об основной функции языка Java — аннотациях по умолчанию, доступных в JDK.
2. Что такое аннотация
Проще говоря, аннотации — это типы Java, которым предшествует символ «@» .
Аннотации в Java появились еще с версии 1.5. С тех пор они повлияли на то, как мы разрабатываем наши приложения.
Spring и Hibernate — отличные примеры фреймворков, которые в значительной степени полагаются на аннотации для реализации различных методов проектирования.
По сути, аннотация назначает дополнительные метаданные исходному коду, к которому она привязана . Добавляя аннотацию к методу, интерфейсу, классу или полю, мы можем:
- Информировать компилятор о предупреждениях и ошибках
- Манипулировать исходным кодом во время компиляции
- Измените или проверьте поведение во время выполнения
3. Встроенные аннотации Java
Теперь, когда мы рассмотрели основы, давайте взглянем на некоторые аннотации, поставляемые с ядром Java. Во-первых, есть несколько, которые информируют о компиляции:
@Override
@SuppressWarnings
@Устаревший
@SafeVarargs
@ФункциональныйИнтерфейс
@Родной
Эти аннотации генерируют или подавляют предупреждения и ошибки компилятора. Их последовательное применение часто является хорошей практикой, поскольку их добавление может предотвратить будущие ошибки программиста.
Аннотация @Override
используется , чтобы указать, что метод переопределяет или заменяет поведение унаследованного метода.
@SuppressWarnings
указывает, что мы хотим игнорировать определенные предупреждения из части кода. Аннотация @SafeVarargs
также действует на предупреждение, связанное с использованием varargs.
Аннотацию @Deprecated
можно использовать, чтобы пометить API как не предназначенный для использования. Кроме того, эта аннотация была модифицирована в Java 9 , чтобы предоставить больше информации об устаревании.
Более подробную информацию обо всем этом вы можете найти в статьях по ссылкам.
3.1. @ФункциональныйИнтерфейс
Java 8 позволяет нам писать код более функциональным способом.
Интерфейсы с одним абстрактным методом являются большой частью этого. Если мы предполагаем, что интерфейс SAM будет использоваться лямбда-выражениями, мы можем дополнительно пометить его как таковой с помощью @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
, эта аннотация защищает нас от будущих ошибок программиста. Несмотря на то, что допустимо иметь более одного метода на интерфейсе, это не так, когда этот интерфейс используется в качестве лямбда-цели. Без этой аннотации компилятор сломался бы в десятках мест, где Adder
использовался как лямбда. Теперь он просто ломается в самом Adder
.
3.2. @Родной
Начиная с Java 8, в пакете java.lang.annotation
появилась новая аннотация под названием Native .
Аннотация @Native
применима только к полям. Это указывает на то, что аннотированное поле является константой, на которую можно ссылаться из собственного кода . Например, вот как это используется в классе Integer
:
public final class Integer {
@Native public static final int MIN_VALUE = 0x80000000;
// omitted
}
Эта аннотация также может служить подсказкой для инструментов для создания некоторых вспомогательных заголовочных файлов.
4. Мета-аннотации
Далее, мета-аннотации — это аннотации, которые можно применять к другим аннотациям.
Например, эти мета-аннотации используются для настройки аннотаций:
@Цель
@Удержание
@Унаследовано
@Документировано
@Repeatable
4.1. @Цель
Объем аннотаций может варьироваться в зависимости от требований. В то время как одна аннотация используется только с методами, другая аннотация может использоваться с конструкторами и объявлениями полей.
Чтобы определить целевые элементы пользовательской аннотации, нам нужно пометить ее аннотацией @Target
.
@Target
может работать с 12 различными типами элементов . Если мы посмотрим на исходный код @SafeVarargs
, то увидим, что он должен быть привязан только к конструкторам или методам:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}
4.2. @Удержание
Некоторые аннотации предназначены для использования в качестве подсказок для компилятора, а другие используются во время выполнения.
Мы используем аннотацию @Retention
, чтобы указать, где в жизненном цикле нашей программы применяется наша аннотация .
Для этого нам нужно настроить @Retention
с одной из трех политик хранения:
RetentionPolicy.SOURCE —
не виден ни компилятору, ни среде выполнения.RetentionPolicy.CLASS
— виден компиляторуRetentionPolicy.RUNTIME —
виден компилятору и среде выполнения.
Если в объявлении аннотации отсутствует аннотация @Retention ,
политика хранения по умолчанию имеет значение RetentionPolicy.CLASS
.
Если у нас есть аннотация, которая должна быть доступна во время выполнения:
@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface RetentionAnnotation {
}
Затем, если мы добавим некоторые аннотации к классу:
@RetentionAnnotation
@Generated("Available only on source code")
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
, а @Generated
— нет.
4.3. @Унаследовано
В некоторых ситуациях нам может понадобиться подкласс, чтобы аннотации были привязаны к родительскому классу.
Мы можем использовать аннотацию @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. @Документировано
По умолчанию Java не документирует использование аннотаций в Javadocs.
Но мы можем использовать аннотацию @Documented
, чтобы изменить поведение Java по умолчанию .
Если мы создадим пользовательскую аннотацию, которая использует @Documented
:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
int value();
}
И примените его к соответствующему элементу Java:
public class Employee {
@ExcelCell(0)
public String name;
}
Затем Javadoc Employee
покажет использование аннотации:
4.5. @Repeatable
Иногда бывает полезно указать одну и ту же аннотацию более одного раза для данного элемента Java.
До Java 7 нам приходилось группировать аннотации в одну аннотацию контейнера:
@Schedules({
@Schedule(time = "15:05"),
@Schedule(time = "23:00")
})
void scheduledAlarm() {
}
Однако Java 7 привнесла более чистый подход. С помощью аннотации @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.
Как всегда, все примеры статьи можно найти на GitHub .