Перейти к основному содержимому

Обзор встроенных аннотаций Java

· 7 мин. чтения

1. Обзор

В этой статье мы поговорим об основной функции языка Java — аннотациях по умолчанию, доступных в JDK.

2. Что такое аннотация

Проще говоря, аннотации — это типы Java, которым предшествует символ «@» .

Аннотации в Java появились еще с версии 1.5. С тех пор они повлияли на то, как мы разрабатываем наши приложения.

Spring и Hibernate — отличные примеры фреймворков, которые в значительной степени полагаются на аннотации для реализации различных методов проектирования.

По сути, аннотация назначает дополнительные метаданные исходному коду, к которому она привязана . Добавляя аннотацию к методу, интерфейсу, классу или полю, мы можем:

  1. Информировать компилятор о предупреждениях и ошибках
  2. Манипулировать исходным кодом во время компиляции
  3. Измените или проверьте поведение во время выполнения

3. Встроенные аннотации Java

Теперь, когда мы рассмотрели основы, давайте взглянем на некоторые аннотации, поставляемые с ядром Java. Во-первых, есть несколько, которые информируют о компиляции:

  1. @Override
  2. @SuppressWarnings
  3. @Устаревший
  4. @SafeVarargs
  5. @ФункциональныйИнтерфейс
  6. @Родной

Эти аннотации генерируют или подавляют предупреждения и ошибки компилятора. Их последовательное применение часто является хорошей практикой, поскольку их добавление может предотвратить будущие ошибки программиста.

Аннотация @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. Мета-аннотации

Далее, мета-аннотации — это аннотации, которые можно применять к другим аннотациям.

Например, эти мета-аннотации используются для настройки аннотаций:

  1. @Цель
  2. @Удержание
  3. @Унаследовано
  4. @Документировано
  5. @Repeatable

4.1. @Цель

Объем аннотаций может варьироваться в зависимости от требований. В то время как одна аннотация используется только с методами, другая аннотация может использоваться с конструкторами и объявлениями полей.

Чтобы определить целевые элементы пользовательской аннотации, нам нужно пометить ее аннотацией @Target .

@Target может работать с 12 различными типами элементов . Если мы посмотрим на исходный код @SafeVarargs , то увидим, что он должен быть привязан только к конструкторам или методам:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}

4.2. @Удержание

Некоторые аннотации предназначены для использования в качестве подсказок для компилятора, а другие используются во время выполнения.

Мы используем аннотацию @Retention , чтобы указать, где в жизненном цикле нашей программы применяется наша аннотация .

Для этого нам нужно настроить @Retention с одной из трех политик хранения:

  1. RetentionPolicy.SOURCE — не виден ни компилятору, ни среде выполнения.
  2. RetentionPolicy.CLASS — виден компилятору
  3. 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 покажет использование аннотации:

./1c5a11e0ca7f9dbb815dfb893f5b4fa5.png

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 .