Преглед на вградените анотации на Java

1. Общ преглед

В тази статия ще говорим за основна характеристика на езика Java - анотациите по подразбиране, налични в JDK.

2. Какво представлява анотацията

Най-просто казано, анотациите са типове Java, които са предшествани от символ „@“ .

Java има анотации още от издаването на 1.5. Оттогава те оформиха начина, по който проектирахме нашите приложения.

Spring и Hibernate са чудесни примери за рамки, които разчитат силно на анотации, за да дадат възможност за различни техники на проектиране.

По принцип, анотация присвоява допълнителни метаданни на изходния код, към който е обвързан . Чрез добавяне на анотация към метод, интерфейс, клас или поле можем да:

  1. Информирайте компилатора за предупреждения и грешки
  2. Манипулирайте изходния код по време на компилация
  3. Модифицирайте или проверете поведението по време на изпълнение

3. Вградени анотации на Java

Сега, след като прегледахме основите, нека да разгледаме някои анотации, които се доставят с основната Java. Първо, има няколко, които информират компилацията:

  1. @Override
  2. @SuppressWarnings
  3. @ Поощрено
  4. @SafeVarargs
  5. @FunctionalInterface
  6. @Местен

Тези пояснения генерират или потискат предупреждения и грешки на компилатора. Прилагането им последователно често е добра практика, тъй като добавянето им може да предотврати бъдещи грешки на програмиста.

В @Override анотацията се използва за обозначаване, че метод замени или подмени поведението на наследствено метод.

@SuppressWarnings показва, че искаме да игнорираме определени предупреждения от част от кода. В @SafeVarargs анотацията действа и на тип предупреждения, свързани с използване на varargs.

В @deprecated анотацията може да се използва за отбелязване на API като не е предназначен за употреба вече. Освен това тази анотация е модернизирана в Java 9, за да представлява повече информация за оттеглянето.

За всичко това можете да намерите по-подробна информация в свързаните статии.

3.1. @FunctionalInterface

Java 8 ни позволява да пишем код по по-функционален начин.

Интерфейсите с един абстрактен метод са голяма част от това. Ако възнамеряваме да използваме SAM интерфейс от lambdas, можем по желание да го маркираме като такъв с @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. @ Повторно

4.1. @Цел

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

За да определим целевите елементи на персонализирана анотация, трябва да я обозначим с анотация @Target .

@Target може да работи с осем различни типа елементи. Ако разгледаме изходния код на @ SafeVarargs , тогава можем да видим, че той трябва да бъде прикрепен само към конструктори или методи:

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

4.2. @ Задържане

Някои пояснения са предназначени да бъдат използвани подсказки за компилатора, докато други се използват по време на изпълнение.

Използваме анотацията @Retention, за да кажем къде в жизнения цикъл на нашата програма се отнася нашата анотация .

To do this, we need to configure @Retention with one of three retention policies:

  1. RetentionPolicy.SOURCE – visible by neither the compiler nor the runtime
  2. RetentionPolicy.CLASS – visible by the compiler
  3. RetentionPolicy.RUNTIME – visible by the compiler and the runtime

@Retention defaults to RetentionPolicy.SOURCE.

If we have an annotation that should be accessible at runtime:

@Retention(RetentionPolicy.RUNTIME) @Target(TYPE) public @interface RetentionAnnotation { }

Then, if we add some annotations to a class:

@RetentionAnnotation @Deprecated public class AnnotatedClass { }

Now we can reflect on AnnotatedClass to see how many annotations are retained:

@Test public void whenAnnotationRetentionPolicyRuntime_shouldAccess() { AnnotatedClass anAnnotatedClass = new AnnotatedClass(); Annotation[] annotations = anAnnotatedClass.getClass().getAnnotations(); assertThat(annotations.length, is(1)); }

The value is 1 because @RetentionAnnotation has a retention policy of RUNTIME while @Deprecated doesn't.

4.3. @Inherited

In some situations, we may need a subclass to have the annotations bound to a parent class.

We can use the @Inherited annotation to make our annotation propagate from an annotated class to its subclasses.

If we apply @Inherited to our custom annotation and then apply it to BaseClass:

@Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface InheritedAnnotation { } @InheritedAnnotation public class BaseClass { } public class DerivedClass extends BaseClass { }

Then, after extending the BaseClass, we should see that DerivedClass appears to have the same annotation at runtime:

@Test public void whenAnnotationInherited_thenShouldExist() { DerivedClass derivedClass = new DerivedClass(); InheritedAnnotation annotation = derivedClass.getClass() .getAnnotation(InheritedAnnotation.class); assertThat(annotation, instanceOf(InheritedAnnotation.class)); }

Without the @Inherited annotation, the above test would fail.

4.4. @Documented

By default, Java doesn't document the usage of an annotation in Javadocs.

But, we can use the @Documented annotation to change Java's default behavior.

If we create a custom annotation that uses @Documented:

@Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ExcelCell { int value(); }

And, apply it to the appropriate Java element:

public class Employee { @ExcelCell(0) public String name; }

Then, the Employee Javadoc will reveal the annotation usage:

4.5. @Repeatable

Sometimes it can be useful to specify the same annotation more than once on a given Java element.

Before Java 7, we had to group annotations together into a single container annotation:

@Schedules({ @Schedule(time = "15:05"), @Schedule(time = "23:00") }) void scheduledAlarm() { }

However, Java 7 brought a cleaner approach. With the @Repeatable annotation, we can make an annotation repeatable:

@Repeatable(Schedules.class) public @interface Schedule { String time() default "09:00"; }

To use @Repeatable, we need to have a container annotation, too. In this case, we'll reuse @Schedules:

public @interface Schedules { Schedule[] value(); }

Of course, this looks a lot like what we had before Java 7. But, the value now is that the wrapper @Schedules isn't specified anymore when we need to repeat @Schedule:

@Schedule @Schedule(time = "15:05") @Schedule(time = "23:00") void scheduledAlarm() { }

Because Java requires the wrapper annotation, it was easy for us to migrate from pre-Java 7 annotation lists to repeatable annotations.

5. Conclusion

В тази статия говорихме за вградени в Java анотации, с които всеки разработчик на Java трябва да е запознат.

Както винаги, всички примери за статията могат да бъдат намерени в GitHub.