Аннотации
Аннотация — это пометка в коде, которая начинается с @: @Override, @Test, @Deprecated. Сама по себе она ничего не делает — это дополнительная информация (метаданные) для компилятора, инструментов сборки и библиотек. Например, по аннотации @Test библиотека JUnit находит методы-тесты, а Spring по своим аннотациям настраивает приложение.
@Override
Самая частая аннотация — @Override. Она говорит компилятору: «этот метод переопределяет метод родителя или интерфейса». Если это не так, компилятор сообщит об ошибке:
class Animal {
String sound() {
return "...";
}
}
class Cat extends Animal {
@Override
String sound() {
return "Мяу";
}
}
void main() {
Animal pet = new Cat();
IO.println(pet.sound());
}Результат:
МяуБез @Override код тоже работает. Но аннотация защищает от опечаток: если написать @Override String sounds(), программа не скомпилируется.
class Animal {
String sound() {
return "...";
}
}
class Cat extends Animal {
@Override
String sounds() {
return "Мяу";
}
}
void main() {
IO.println(new Cat().sound());
}Результат:
Main.java:8: error: method does not override or implement a method from a supertype
@Override
^
1 error
error: compilation failedДругие встроенные аннотации
@Deprecated— элемент устарел и его не стоит использовать. Компилятор предупредит каждого, кто его вызовет.@SuppressWarnings("unchecked")— просит компилятор не выводить определённое предупреждение.@FunctionalInterface— интерфейс должен иметь ровно один абстрактный метод, чтобы его можно было реализовать лямбдой. Если добавить второй, компилятор сообщит об ошибке.
@FunctionalInterface
interface Discount {
double apply(double price);
}
void main() {
Discount blackFriday = price -> price * 0.7;
IO.println(blackFriday.apply(100));
}Результат:
70.0Своя аннотация
Аннотацию объявляют словом @interface. Внутри перечисляют её элементы — они записываются как методы без параметров и могут иметь значения по умолчанию:
// Command.java
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
String name();
String description() default "без описания";
}Две аннотации над объявлением — мета-аннотации, то есть аннотации для аннотаций:
@Retentionзадаёт, как долго аннотация хранится.RUNTIME— она доступна во время работы программы. Без этого её увидит только компилятор.@Targetзадаёт, что можно пометить:METHOD— только методы,TYPE— классы,FIELD— поля.
Чтение аннотаций
Аннотации с RUNTIME можно прочитать через рефлексию — механизм, который позволяет программе изучать свои классы и методы. Подробно он разобран в следующем уроке. Найдём все методы-команды и выведем их описание:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
String name();
String description() default "без описания";
}
class Bot {
@Command(name = "/start", description = "приветствие")
void start() {}
@Command(name = "/help")
void help() {}
void internal() {}
}
void main() {
List<Method> methods = new ArrayList<>(List.of(Bot.class.getDeclaredMethods()));
methods.sort(Comparator.comparing(Method::getName));
for (Method method : methods) {
Command command = method.getAnnotation(Command.class);
if (command != null) {
IO.println(command.name() + " — " + command.description());
}
}
}Результат:
/help — без описания
/start — приветствиеМетод internal не помечен, и getAnnotation вернул для него null. Порядок методов в getDeclaredMethods не гарантирован, поэтому список отсортирован по имени. Именно так фреймворки находят обработчики команд, URL-адреса и тесты.
Частая ошибка
Если забыть @Retention(RetentionPolicy.RUNTIME), аннотация по умолчанию не попадёт в работающую программу, и рефлексия её не увидит:
@interface Command {
String name();
}
class Bot {
@Command(name = "/start")
void start() {}
}
void main() throws NoSuchMethodException {
Method method = Bot.class.getDeclaredMethod("start");
IO.println(method.isAnnotationPresent(Command.class));
}Результат:
falseОшибки нет, просто аннотация «пропала». Для аннотаций, которые читаются во время работы, всегда указывайте RUNTIME.