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

Обзор встроенных аннотаций 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. Добавляя аннотацию к методу, интерфейсу, классу или полю, мы можем:

  1. Сообщите компилятору о предупреждениях и ошибках

  2. Манипулировать исходным кодом во время компиляции

  3. Изменить или изучить поведение во время выполнения

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

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

  1. @Override

  2. @SuppressWarnings

  3. @Deprecated

  4. @SafeVarargs

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

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

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

  1. @Target

  2. @Retention

  3. @Inherited

  4. @ Документированный

  5. @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 с одной из трех политик хранения:

  1. RetentionPolicy.SOURCE – не виден ни компилятором, ни средой выполнения

  2. RetentionPolicy.CLASS - виден компилятором

  3. 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 покажет использование аннотации:

image

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.