Створення анотацій – процес доволі простий, хоч і обмежений деякими правилами. Тепер нам потрібно розібратися, у чому полягає їхня користь на практиці.

Давай пригадаємо, як ми створюємо свою анотацію.

Пишемо анотацію, яка буде анотувати класи та методи й містити інформацію про автора й версію:


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Info {
   String author() default "Author";
   String version() default "0.0";
}

Наші класи, до яких ми додали анотацію:


@Info
public class MyClass1 {
   @Info
   public void myClassMethod() {}
}
 
@Info(version = "2.0")
public class MyClass2 {
   @Info(author = "Anonymous")
   public void myClassMethod() {}
}
 
@Info(author = "Anonymous", version = "2.0")
public class MyClass3 {
   @Info(author = "Anonymous", version = "4.0")
   public void myClassMethod() {}
}

Як нам скористатися цими даними на етапі роботи програми?

Витягти метадані з анотацій можна за допомогою рефлексії. Згадаймо, що таке рефлексія. Це механізм дослідження даних про програму під час виконання. Рефлексія дозволяє отримувати інформацію про поля, методи, конструктори класів, а також про класи.

За допомогою рефлексії ми прочитаємо анотації у класі та виведемо необхідну нам інформацію.

Розпізнаємо дані з наших класів у main:


import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
 
public class Main {
   public static void main(String[] args) throws NoSuchMethodException {
       readMyClass(MyClass1.class);
       readMyClass(MyClass2.class);
       readMyClass(MyClass3.class);
   }
 
   static void readMyClass(Class<?> myClassObj) throws NoSuchMethodException {
       System.out.println("\nКлас " + myClassObj.getName());
       readAnnotation(myClassObj);
       Method method = myClassObj.getMethod("myClassMethod");
       readAnnotation(method);
   }
 
   static void readAnnotation(AnnotatedElement element) {
       try {
           System.out.println("Пошук анотацій в " + element.getClass().getName());
           Annotation[] annotations = element.getAnnotations();
           for (Annotation annotation : annotations) {
               if (annotation instanceof Info) {
                   final Info fileInfo = (Info) annotation;
                   System.out.println("Автор: " + fileInfo.author());
                   System.out.println("Версія: " + fileInfo.version());
               }
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

До методу readMyClass ми передаємо екземпляр нашого класу на обробку.

Далі до методу readAnnotation ми можемо передати і клас, і метод. Так і зробимо: передаємо туди об'єкт класу та об'єкт методу. Приймається об'єкт, який реалізує контракт AnnotatedElement. Це дозволяє дістати з нього список анотацій та прочитати інформацію щодо кожної з них.

Зверни увагу на те, що ми не дістанемо інформацію, не перевіривши належність анотації до нашого типу анотації (if (annotation instanceof Info)).

На виході ми отримуємо повну інформацію з анотацій:

Клас annotation.MyClass1
Пошук анотацій в java.lang.Class
Автор: Author
Версія: 0.0
Пошук анотацій в java.lang.reflect.Method
Автор: Author
Версія: 0.0

Клас annotation.MyClass2
Пошук анотацій в java.lang.Class
Автор: Author
Версія: 2.0
Пошук анотацій в java.lang.reflect.Method
Автор: Anonymous
Версія: 0.0

Клас annotation.MyClass3
Пошук анотацій в java.lang.Class
Автор: Anonymous
Версія: 2.0
Пошук анотацій в java.lang.reflect.Method
Автор: Anonymous
Версія: 4.0

Так за допомогою рефлексії ми змогли дістати метаінформацію.

Тепер розглянемо приклад використання анотацій для вдосконалення коду, зокрема підвищення легкості читання, швидкості роботи та загальної якості. У цьому нам допоможе Lombok.

Lombok — це плагін-надбудова компілятора, який за допомогою ключових слів-анотацій приховує величезну кількість коду і таким чином розширє мову, спрощує розробку та додає деяку функціональність.

Розглянемо приклад анотацій з Lombok:

@ToString Генерує реалізацію для метода toString(), яка складається з акуратного представлення об'єкта: імені класу, всіх полів та їхнього значення.

@ToString
public class Example
@EqualsAndHashCode Генерує реалізації equals та hashCode, які за замовчуванням використовують нестатичні та нестаціонарні поля, але налаштовуються. Детальніше можна прочитати на сайті проєкту. Там описано приклад із використанням @EqualsAndHashCode і без нього, зі стандартною реалізацією.
@Getter / @Setter Генерує гетери та сетери для приватних полів.

@Getter 
@Setter 
private String name = “name”;
@NonNull Використовуються для твердження, що поля не є null під час створення екземпляра об'єкта. Інакше викидається виняток NullPointerException.

public Example(@NonNull P p) {
 super("Hello");
 this.name = p.getName();
}

У Lombok є ще багато корисних анотацій, які використовуються рідше. Ми розглянули просту частину його анотацій. Про сам проєкт можна прочитати більше на офіційному сайті.