Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Аннотации

Аннотация — это пометка в коде, которая начинается с @: @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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.