☕ Вопросы интервью по Java

Вопросы 201-210 - Аннотации

Вопрос 201
Что такое аннотации в Java и зачем они нужны?

Ответ: Аннотация — это форма метаданных, которую можно добавить к классам, методам, полям, параметрам, пакетам и другим элементам кода (с Java 5). Сама по себе аннотация не меняет логику кода — её обрабатывает компилятор, инструмент или фреймворк (во время компиляции или выполнения).

Для чего используются:
  • Информация для компилятора — например, @Override проверяет, что метод действительно переопределяет родительский
  • Генерация кода при компиляции — Lombok, MapStruct, Dagger
  • Конфигурация во время выполнения — Spring (@Autowired, @Transactional), Hibernate (@Entity, @Column), JUnit (@Test)
  • Валидация — Bean Validation (@NotNull, @Size)
Пример:

public class Animal {
    public void voice() { }
}

public class Dog extends Animal {
    @Override                       // компилятор проверит имя и сигнатуру
    public void voice() {
        System.out.println("Гав");
    }
}
                
💡 Важно: аннотация сама ничего не делает — нужен код (компилятор, annotation processor или рефлексия), который её читает и реагирует.
Вопрос 202
Какие встроенные аннотации есть в Java?

Ответ: В java.lang есть несколько стандартных аннотаций для компилятора:

Аннотация Назначение
@Override Метод переопределяет метод суперкласса или реализует метод интерфейса; иначе ошибка компиляции
@Deprecated Элемент устарел, компилятор выдаёт предупреждение при использовании. Параметры (с Java 9): since, forRemoval
@SuppressWarnings Подавляет предупреждения компилятора, например "unchecked", "deprecation"
@FunctionalInterface Проверяет, что интерфейс содержит ровно один абстрактный метод
@SafeVarargs Говорит, что метод с varargs обобщённого типа не нарушает типобезопасность (heap pollution)
Пример:

@FunctionalInterface
interface Validator<T> {
    boolean validate(T value);
    // второй абстрактный метод здесь вызовет ошибку компиляции
}

@Deprecated(since = "2.0", forRemoval = true)
public void oldMethod() { }

@SuppressWarnings("unchecked")
List<String> list = (List<String>) rawObject;
                
Вопрос 203
Что такое мета-аннотации?

Ответ: Мета-аннотации — это аннотации, которыми помечают другие аннотации, чтобы описать их поведение. Находятся в java.lang.annotation.

Мета-аннотация Что задаёт
@Retention Как долго аннотация сохраняется: SOURCE, CLASS или RUNTIME
@Target К каким элементам можно применять (класс, метод, поле, параметр и т.д.)
@Documented Аннотация попадает в сгенерированный Javadoc
@Inherited Аннотация на классе наследуется подклассами
@Repeatable Аннотацию можно указать несколько раз на одном элементе (с Java 8)
Пример:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface Audit { }
                
Вопрос 204
Что такое RetentionPolicy: SOURCE, CLASS и RUNTIME?

Ответ: @Retention определяет, на каком этапе аннотация ещё доступна.

Политика Где доступна Примеры
SOURCE Только в исходном коде, компилятор её отбрасывает @Override, @SuppressWarnings
CLASS В .class файле, но недоступна через рефлексию во время выполнения значение по умолчанию, если @Retention не указан
RUNTIME В .class файле и доступна через Reflection @Deprecated, @FunctionalInterface, аннотации Spring и Hibernate
💡 Важно: чтобы прочитать аннотацию через рефлексию, у неё обязательно должен быть @Retention(RetentionPolicy.RUNTIME). Частая ошибка — забыть его и получить null из getAnnotation().
Вопрос 205
Как создать собственную аннотацию?

Ответ: Аннотация объявляется с помощью ключевого слова @interface. Параметры задаются как методы без аргументов, значения по умолчанию — через default.

Пример:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Timed {
    String name() default "";       // необязательный параметр
    int warnAfterMs() default 1000;
}

public class OrderService {
    @Timed(name = "createOrder", warnAfterMs = 200)
    public void createOrder() { }

    @Timed                           // все параметры по умолчанию
    public void cancelOrder() { }
}
                
Правила:
  • Методы аннотации не принимают аргументов и не бросают исключений
  • Если параметр один и называется value, его имя можно опустить: @Role("ADMIN")
  • Параметр без default обязателен при использовании
  • Аннотация не может наследоваться от других классов и интерфейсов (неявно расширяет java.lang.annotation.Annotation)
Вопрос 206
Как прочитать аннотации во время выполнения?

Ответ: Через Reflection API. Классы Class, Method, Field, Parameter реализуют AnnotatedElement с методами getAnnotation(), isAnnotationPresent(), getAnnotations().

Пример (продолжение предыдущего вопроса):

for (Method method : OrderService.class.getDeclaredMethods()) {
    if (method.isAnnotationPresent(Timed.class)) {
        Timed timed = method.getAnnotation(Timed.class);
        System.out.println(method.getName()
            + " -> warnAfterMs=" + timed.warnAfterMs());
    }
}
// createOrder -> warnAfterMs=200
// cancelOrder -> warnAfterMs=1000
                
Как это используют фреймворки:
  • Spring сканирует классы, находит @Component, @Transactional и создаёт бины и прокси
  • JUnit находит методы с @Test и вызывает их
  • Jackson читает @JsonProperty при сериализации
💡 Важно: рефлексия медленнее прямого вызова, поэтому фреймворки обычно читают аннотации один раз при старте и кэшируют результат.
Вопрос 207
Какие типы могут быть у параметров аннотации?

Ответ: Параметры аннотации ограничены набором типов:

  • Примитивы (int, boolean, long и т.д.)
  • String
  • Class (в том числе Class<? extends Foo>)
  • enum
  • Другая аннотация
  • Одномерный массив любого из перечисленных типов
Ограничения:
  • Значение должно быть константой времени компиляции — вызвать метод или создать объект нельзя
  • null использовать нельзя, ни как значение, ни как default
  • Обёртки (Integer) и произвольные классы не допускаются
Пример:

public @interface Config {
    String name();
    int retries() default 3;
    Class<?> handler() default Void.class;
    Level level() default Level.INFO;      // enum
    String[] tags() default {};            // массив
}

@Config(name = "job", tags = {"a", "b"})
class Job { }
                
Вопрос 208
Для чего нужны @Inherited и @Repeatable?

Ответ:

@Inherited

Если класс помечен такой аннотацией, то она считается присутствующей и у его подклассов. Работает только для аннотаций на классах — не для методов, полей и интерфейсов.


@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Entity { }

@Entity
class Base { }
class Child extends Base { }

Child.class.isAnnotationPresent(Entity.class);  // true
                
@Repeatable (Java 8)

Позволяет указать одну аннотацию несколько раз на одном элементе. Для этого нужна аннотация-контейнер с массивом value().


@Retention(RetentionPolicy.RUNTIME)
@interface Roles {
    Role[] value();                  // контейнер
}

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Roles.class)
@interface Role {
    String value();
}

@Role("ADMIN")
@Role("AUDITOR")
class Report { }

Role[] roles = Report.class.getAnnotationsByType(Role.class);
                
💡 Важно: при повторных аннотациях getAnnotation(Role.class) вернёт null, так как фактически на классе стоит контейнер Roles. Используйте getAnnotationsByType().
Вопрос 209
Чем аннотации отличаются от маркерных интерфейсов?

Ответ: Маркерный интерфейс (Serializable, Cloneable) — это пустой интерфейс, который помечает класс. Аннотация-маркер (например, @Entity) делает то же самое, но другим способом.

Критерий Маркерный интерфейс Аннотация
Тип Определяет тип — можно использовать в сигнатурах и проверках типов Не создаёт тип
Проверка Ошибка на этапе компиляции при неверном типе Обычно проверяется в runtime через рефлексию
Применимость Только классы Классы, методы, поля, параметры, пакеты и др.
Параметры Нет Да, с значениями по умолчанию
💡 Совет: если нужно принимать помеченные объекты в методе (void save(Serializable s)), используйте маркерный интерфейс. Если нужно пометить метод/поле или передать параметры — аннотацию.
Вопрос 210
Что такое Annotation Processing и как он работает?

Ответ: Annotation Processing (JSR 269) — механизм, при котором компилятор javac вызывает пользовательские обработчики (наследники AbstractProcessor) во время компиляции. Обработчик видит аннотированные элементы и может сгенерировать новые файлы или выдать ошибки и предупреждения.

Как это работает:
  • Компилятор находит аннотации в исходниках
  • Для подходящих аннотаций (@SupportedAnnotationTypes) вызывается process()
  • Процессор может создать новые исходники через Filer, они тоже компилируются
  • Цикл повторяется, пока не перестанут появляться новые файлы
Где используется:
  • MapStruct — генерирует реализации мапперов
  • Dagger — генерирует код внедрения зависимостей
  • Hibernate JPA Metamodel — генерирует статическую метамодель
  • Lombok — тоже работает как процессор, но изменяет AST компилятора через внутренние API
Пример заготовки процессора:

@SupportedAnnotationTypes("com.example.Builder")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class BuilderProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment roundEnv) {
        for (Element e : roundEnv.getElementsAnnotatedWith(Builder.class)) {
            // сгенерировать класс-билдер через processingEnv.getFiler()
        }
        return true;
    }
}
                
💡 Важно: в отличие от рефлексии, обработка идёт при сборке и не тратит время при выполнении программы. Для таких аннотаций обычно хватает RetentionPolicy.SOURCE.