Аннотации в языке программирования Java представляют специальную форму метаданных (данных о данных), которую можно добавлять в исходный код. Они предоставляют дополнительную информацию о программе, которая может использоваться компилятором, инструментами сборки, фреймворками или во время выполнения. Аннотации позволяет отделить конфигурацию и метаданные от основной бизнес-логики. Они делают код более чистым, декларативным и легким для поддержки, перенося большую часть "магии" настройки на фреймворки и инструменты. Можно сказать, что аннотации являются основой многих современных Java-технологий, в частности, веб-фреймворков, фреймворков для тестирования, валидации и т.д.
Сами по себе аннотации не выполняют никаких действий. Они лишь "маркируют" классы, методы, поля и другие элементы кода, а уже другой код (обработчик аннотаций) ищет эти маркеры и выполняет на их основе некоторую определенную логику.
Синтаксически аннотация начинается с символа @, за которым следует ее имя, например, @Override.
В языке Java есть ряд встроенных аннотаций, которые используются для стандартных задач. Рассмотрим некоторые наиболее используемые:
@Override: указывает компилятору, что текущий метод переопределяет метод из суперкласса.
@Deprecated: эта аннотация помечает элемент (класс, метод, поле) как устаревший
@SuppressWarnings: отключает определенные предупреждения компилятора для аннотированного элемента.
@FunctionalInterface: указывает, что интерфейс предназначен для использования в качестве функционального интерфейса (т.е. он должен иметь ровно один абстрактный метод).
Аннотации объявлений могут применяться ко многим компонентам. И в зависимости от места применения аннотации делятся на 2 группы:
аннотации объявлений (declaration annotation)
аннотации использования типов (type uses annotation)
Аннотации объявлений содержат некоторую информацию об объявляемом элементе и могут применяться к следующим объявлениям:
К классам (включая перечисления) и интерфейсам (в том числе к интерфейсам аннотаций)
К методам
К конструкторам
К полям (в том числе к константам перечислений и полям классов record).
К локальным переменным (в том числе к объявленным в конструкциях for и try-with-resources).
К параметрам методов
К параметрам типов
К пакетам и модулям
Для классов, интерфейсов и модулей аннотации размещаются перед ключевым словом class, interface или module и любым модификаторам (при его наличии):
@MyAnnotation public class Person { }
Для переменных, параметров, а также поле классов-record аннотации размещаются перед типом:
@MyAnnotation String name = "Tom";
public User getUser(@MyAnnotation int userId)
record Person(@MyAnnotation String name) {}
Также аннотация помещается перед параметром типа в обобщенном классе или методе:
class Person<@MyAnnotation T> { }
Аннотации использования типов могут появляться в следующих местах:
С аргументами обобщенного типа:
Person<@MyAnnotation String>
Перед скобками массива:
String[] @MyAnnotation [] data // Применяется к String[] String @MyAnnotation [][] data // Применяется к String[][]
От эих ситуаций надо отличать следующую:
@MyAnnotation String[][] data
Здесь аннотация применяется к String
С суперклассами и реализованными интерфейсами:
class Employee extends @MyAnnotation Person.
С вызовами конструктора
new @MyAnnotation Person();
С вложенными типами
Map.@MyAnnotation Entry
С приведениями типов и проверками instanceof:
(@MyAnnotation String) text if (text instanceof @MyAnnotation String)
Аннотации предназначены только для использования внешними инструментами. Они не влияют на поведение приведения типов или проверки instanceof.
С указанием генерируемых исключений
void print() throws @MyAnnotation Exception.
С подстановочными типами и ограничениями типов
List<@MyAnnotation ? extends Person> List<? extends @MyAnnotation Person>
С ссылками на методы и конструктор:
@MyAnnotation Person::getName
Аннотации можно помещать до или после других модификаторов, таких как static и public.
Обычно аннотации использования типа размещаются после других модификаторов, а аннотации объявлений - перед другими модификаторами. Например:
private @MyAnnotation String name; // Аннотация использования типа @MyAnnotation private int age; // Аннотация переменной / поля класса
Аннотация @Override указывает компилятору, что текущий метод переопределяет метод из суперкласса. Если метод с такой сигнатурой в суперклассе не найден, компилятор выдаст ошибку. Это помогает избежать опечаток.
Конечно, при переопределении метода суперкласса в производном классе нам необязательно использовать данную аннотацию. Но рассмотрим следующую программу:
class Animal {
void makeSound() {
System.out.println("Некий звук");
}
}
class Cat extends Animal {
void makeSounds() {
System.out.println("Мяу");
}
}
class Program{
public static void main(String[] args) throws Exception{
Cat barsik = new Cat();
barsik.makeSound(); // Некий звук
}
}
Класс Animal представляет животное и определяет метод makeSound() для имитации звука животного. Класс Cat переопределяет этот метод. А в методе main() вызываем этот метод. Но посмотрим на
консольный вывод программы:
Некий звук
Результат, очевидно, не тот, который ожидался. А почему? А потому что я допустил опечатку: вместо "makeSound" в классе Cat определил метод "makeSounds". В итоге я по сути определил новый метод, который ничего не переопределяет. Но программа была успешно скомпилирована и успешно выполнила все свои действия! И в том числе для предупреждения подобных ситуаций предназначена аннотация @Override. Применим ее:
class Animal {
void makeSound() {
System.out.println("Некий звук");
}
}
class Cat extends Animal {
@Override
void makeSounds() {
System.out.println("Мяу");
}
}
В производном классе Cat попрежнему метод с опечаткой: "makeSounds" вместо "makeSound". Однако? применив к методу аннотацию @Override, мы указываем, что в суперклассе должен быть метод с подобным именем, и мы его хотим переопределить. Но поскольку в суперклассе нет метода "makeSounds", то при компиляции теперь мы получим ошибку:
Program.java:8: error: method does not override or implement a method from a supertype
@Override
^
1 error
error: compilation failed
Таким образом, мы увидим ошибку и сможем своевременно ее исправить, недопустив некорректного выполнения программы:
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Мяу");
}
}
Аннотация @Deprecated помечает элемент (класс, метод, поле) как устаревший, что может быть полезно, если после обновления API класса или пакета, мы хотим, что какие-то компоненты больше не использовались. Компилятор будет выдавать предупреждение при каждой попытке использовать такой компонент. Например:
class Cat{
@Deprecated
void makeSound() {
System.out.println("Мяу");
}
void say() {
System.out.println("Мяу");
}
}
class Program{
public static void main(String[] args) throws Exception{
Cat barsik = new Cat();
barsik.makeSound();
}
}
Здесь в классе Cat определен метод makeSound(), который с помощью аннотации @Deprecated объявлен как устаревший (допустим, мы хотим, чтобы в будущем разработчики использовали метод
say(), который дублирует функциональность метода makeSound()). Данная программу успешно скомпилируется и будет выполняться, но при компиляции компилятор выдаст предупреждение:
eugene@Eugene:/workspace/java$ java Program.java
Program.java:18: warning: [deprecation] makeSound() in Cat has been deprecated
barsik.makeSound();
^
1 warning
Мяу
Аннотация @SuppressWarnings отключает определенные предупреждения компилятора для аннотированного элемента. Например:
import java.util.ArrayList;
import java.util.List;
class Program{
public static void main(String[] args) throws Exception{
methodWithRawType();
}
public static void methodWithRawType() {
List names = new ArrayList();
names.add("Tom");
System.out.println(names);
}
}
Эта программа успешно скомпилируется и выполняется, однако при компиляции мы получим предупреждение:
eugene@Eugene:/workspace/java$ java Program.java
Program.java:13: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
names.add("Tom");
^
where E is a type-variable:
E extends Object declared in interface List
1 warning
[Tom]
Проблема в том, что нам надо указать тип хранимых данных для ArrayList, наподобие следующего:
List<String> names = new ArrayList<String>();
Однако мы также можем применить аннотацию @SuppressWarnings для отключения подобного (и других) предупреждений. Для этого после названия аннотации в скобках надо передать строку с типом предупреждения. В примере выше мы тип предупреждения - "unchecked", соответственно нам надо написать:
@SuppressWarnings("unchecked")
public static void methodWithRawType() {
List names = new ArrayList();
names.add("Tom");
System.out.println(names);
}
Аннотация @FunctionalInterface указывает, что интерфейс предназначен для использования в качестве функционального интерфейса (т.е. он должен иметь ровно один абстрактный метод). Компилятор проверяет это условие.
@FunctionalInterface
interface MyComparator {
int compare(String a, String b);
}