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

Давай вспомним, как мы создаем свою аннотацию.

Пишем аннотацию, которая будет аннотировать классы и методы и содержать информацию об авторе и версии:


@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 есть еще много полезных аннотаций, которые используются реже. Мы рассмотрели простую часть его аннотаций. Про сам проект можно почитать больше на официальном сайте.