Вопросы 201-210 - Аннотации
Ответ: Аннотация — это форма метаданных, которую можно добавить к классам, методам, полям, параметрам, пакетам и другим элементам кода (с Java 5). Сама по себе аннотация не меняет логику кода — её обрабатывает компилятор, инструмент или фреймворк (во время компиляции или выполнения).
@Override проверяет, что метод действительно переопределяет родительский@Autowired, @Transactional), Hibernate (@Entity, @Column), JUnit (@Test)@NotNull, @Size)
public class Animal {
public void voice() { }
}
public class Dog extends Animal {
@Override // компилятор проверит имя и сигнатуру
public void voice() {
System.out.println("Гав");
}
}
Ответ: В 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;
Ответ: Мета-аннотации — это аннотации, которыми помечают другие аннотации, чтобы описать их поведение. Находятся в 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 { }
Ответ: @Retention определяет, на каком этапе аннотация ещё доступна.
| Политика | Где доступна | Примеры |
|---|---|---|
SOURCE |
Только в исходном коде, компилятор её отбрасывает | @Override, @SuppressWarnings |
CLASS |
В .class файле, но недоступна через рефлексию во время выполнения | значение по умолчанию, если @Retention не указан |
RUNTIME |
В .class файле и доступна через Reflection | @Deprecated, @FunctionalInterface, аннотации Spring и Hibernate |
@Retention(RetentionPolicy.RUNTIME). Частая ошибка — забыть его и получить null из getAnnotation().
Ответ: Аннотация объявляется с помощью ключевого слова @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)Ответ: Через 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
@Component, @Transactional и создаёт бины и прокси@Test и вызывает их@JsonProperty при сериализацииОтвет: Параметры аннотации ограничены набором типов:
int, boolean, long и т.д.)StringClass (в том числе Class<? extends Foo>)enumnull использовать нельзя, ни как значение, ни как defaultInteger) и произвольные классы не допускаются
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 { }
Ответ:
Если класс помечен такой аннотацией, то она считается присутствующей и у его подклассов. Работает только для аннотаций на классах — не для методов, полей и интерфейсов.
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Entity { }
@Entity
class Base { }
class Child extends Base { }
Child.class.isAnnotationPresent(Entity.class); // true
Позволяет указать одну аннотацию несколько раз на одном элементе. Для этого нужна аннотация-контейнер с массивом 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().
Ответ: Маркерный интерфейс (Serializable, Cloneable) — это пустой интерфейс, который помечает класс. Аннотация-маркер (например, @Entity) делает то же самое, но другим способом.
| Критерий | Маркерный интерфейс | Аннотация |
|---|---|---|
| Тип | Определяет тип — можно использовать в сигнатурах и проверках типов | Не создаёт тип |
| Проверка | Ошибка на этапе компиляции при неверном типе | Обычно проверяется в runtime через рефлексию |
| Применимость | Только классы | Классы, методы, поля, параметры, пакеты и др. |
| Параметры | Нет | Да, с значениями по умолчанию |
void save(Serializable s)), используйте маркерный интерфейс. Если нужно пометить метод/поле или передать параметры — аннотацию.
Ответ: Annotation Processing (JSR 269) — механизм, при котором компилятор javac вызывает пользовательские обработчики (наследники AbstractProcessor) во время компиляции. Обработчик видит аннотированные элементы и может сгенерировать новые файлы или выдать ошибки и предупреждения.
@SupportedAnnotationTypes) вызывается process()Filer, они тоже компилируются
@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.