1. Введение
Аннотации существуют со времен Java 5, и в настоящее время они представляют собой повсеместно распространенные программные конструкции, позволяющие обогащать код.
В этой статье мы рассмотрим некоторые вопросы, касающиеся аннотаций; которые часто задают на технических собеседованиях и, где это уместно; мы реализуем примеры, чтобы лучше понять их ответы.
2. Вопросы
Q1. Что такое аннотации? Каковы их типичные варианты использования?
Аннотации — это метаданные, привязанные к элементам исходного кода программы и не влияющие на работу кода, с которым они работают.
Их типичные варианты использования:
- Информация для компилятора — с помощью аннотаций компилятор может обнаружить ошибки или подавить предупреждения.
- Обработка во время компиляции и во время развертывания — программные инструменты могут обрабатывать аннотации и генерировать код, файлы конфигурации и т. д.
- Обработка во время выполнения - аннотации можно просматривать во время выполнения, чтобы настроить поведение программы.
Q2. Опишите некоторые полезные аннотации из стандартной библиотеки.
В пакетах java.lang
и java.lang.annotation
есть несколько аннотаций , наиболее распространенные из которых включают, помимо прочего:
@Override —
отмечает, что метод предназначен для переопределения элемента, объявленного в суперклассе. Если ему не удастся правильно переопределить метод, компилятор выдаст ошибку@Deprecated
— указывает, что элемент устарел и не должен использоваться. Компилятор выдаст предупреждение, если программа использует метод, класс или поле, помеченное этой аннотацией.@SuppressWarnings
— указывает компилятору подавлять определенные предупреждения. Чаще всего используется при взаимодействии с унаследованным кодом, написанным до появления дженериков.@FunctionalInterface
— введен в Java 8, указывает, что объявление типа является функциональным интерфейсом, реализация которого может быть обеспечена с помощью лямбда-выражения.
Q3. Как создать аннотацию?
Аннотации — это форма интерфейса, в которой ключевому слову interface
предшествует @,
а тело содержит объявления элементов типа аннотации
, которые очень похожи на методы:
public @interface SimpleAnnotation {
String value();
int[] types();
}
После того, как аннотация определена, вы можете начать использовать ее в своем коде:
@SimpleAnnotation(value = "an element", types = 1)
public class Element {
@SimpleAnnotation(value = "an attribute", types = { 1, 2 })
public Element nextElement;
}
Обратите внимание, что при указании нескольких значений для элементов массива вы должны заключать их в квадратные скобки.
При желании можно указать значения по умолчанию, если они являются постоянными выражениями для компилятора:
public @interface SimpleAnnotation {
String value() default "This is an element";
int[] types() default { 1, 2, 3 };
}
Теперь вы можете использовать аннотацию без этих элементов:
@SimpleAnnotation
public class Element {
// ...
}
Или только некоторые из них:
@SimpleAnnotation(value = "an attribute")
public Element nextElement;
Q4. Какие типы объектов могут быть возвращены из объявления метода аннотации?
Тип возвращаемого значения должен быть примитивным, String
, Class
, Enum
или массивом одного из предыдущих типов. В противном случае компилятор выдаст ошибку.
Вот пример кода, который успешно следует этому принципу:
enum Complexity {
LOW, HIGH
}
public @interface ComplexAnnotation {
Class<? extends Object> value();
int[] types();
Complexity complexity();
}
Следующий пример не скомпилируется, так как Object
не является допустимым возвращаемым типом:
public @interface FailingAnnotation {
Object complexity();
}
Q5. Какие элементы программы могут быть аннотированы?
Аннотации можно применять в нескольких местах исходного кода. Их можно применять к объявлениям классов, конструкторов и полей:
@SimpleAnnotation
public class Apply {
@SimpleAnnotation
private String aField;
@SimpleAnnotation
public Apply() {
// ...
}
}
Методы и их параметры:
@SimpleAnnotation
public void aMethod(@SimpleAnnotation String param) {
// ...
}
Локальные переменные, включая переменные цикла и ресурсов:
@SimpleAnnotation
int i = 10;
for (@SimpleAnnotation int j = 0; j < i; j++) {
// ...
}
try (@SimpleAnnotation FileWriter writer = getWriter()) {
// ...
} catch (Exception ex) {
// ...
}
Другие типы аннотаций:
@SimpleAnnotation
public @interface ComplexAnnotation {
// ...
}
И даже пакеты через файл package-info.java :
@PackageAnnotation
package com.foreach.interview.annotations;
Начиная с Java 8, их также можно применять к использованию
типов. Чтобы это работало, аннотация должна указывать аннотацию @Target
со значением ElementType.USE
:
@Target(ElementType.TYPE_USE)
public @interface SimpleAnnotation {
// ...
}
Теперь аннотацию можно применить к созданию экземпляра класса:
new @SimpleAnnotation Apply();
Приведение типов:
aString = (@SimpleAnnotation String) something;
Реализует пункт:
public class SimpleList<T>
implements @SimpleAnnotation List<@SimpleAnnotation T> {
// ...
}
И бросает
пункт:
void aMethod() throws @SimpleAnnotation Exception {
// ...
}
Q6. Есть ли способ ограничить элементы, к которым можно применить аннотацию?
Да, для этой цели можно использовать аннотацию @Target .
Если мы попытаемся использовать аннотацию в контексте, где она неприменима, компилятор выдаст ошибку.
Вот пример ограничения использования аннотации @SimpleAnnotation
только объявлениями полей:
@Target(ElementType.FIELD)
public @interface SimpleAnnotation {
// ...
}
Мы можем передать несколько констант, если хотим сделать их применимыми в большем количестве контекстов:
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE })
Мы даже можем сделать аннотацию, чтобы ее нельзя было использовать для аннотирования чего-либо. Это может пригодиться, когда объявленные типы предназначены исключительно для использования в качестве типа-члена в сложных аннотациях:
@Target({})
public @interface NoTargetAnnotation {
// ...
}
Q7. Что такое метааннотации?
Аннотации, которые применяются к другим аннотациям.
Все аннотации, которые не отмечены @Target
или отмечены им, но содержат константу ANNOTATION_TYPE
, также являются мета-аннотациями:
@Target(ElementType.ANNOTATION_TYPE)
public @interface SimpleAnnotation {
// ...
}
Q8. Что такое повторяющиеся аннотации?
Это аннотации, которые можно применять более одного раза к одному и тому же объявлению элемента.
По соображениям совместимости, поскольку эта функция была представлена в Java 8, повторяющиеся аннотации хранятся в аннотации контейнера
, которая автоматически создается компилятором Java. Для этого компилятору необходимо объявить их в два этапа.
Во-первых, нам нужно объявить повторяемую аннотацию:
@Repeatable(Schedules.class)
public @interface Schedule {
String time() default "morning";
}
Затем мы определяем содержащую аннотацию с обязательным элементом значения
, тип которого должен быть массивом повторяемого типа аннотации:
public @interface Schedules {
Schedule[] value();
}
Теперь мы можем использовать @Schedule несколько раз:
@Schedule
@Schedule(time = "afternoon")
@Schedule(time = "night")
void scheduledMethod() {
// ...
}
Q9. Как вы можете получить аннотации? Как это связано с его политикой хранения?
Вы можете использовать Reflection API или процессор аннотаций для получения аннотаций.
Аннотация @Retention
и ее параметр RetentionPolicy
влияют на то, как вы можете их получить. В перечислении RetentionPolicy
есть три константы :
RetentionPolicy.SOURCE
— заставляет компилятор отбрасывать аннотации, но процессоры аннотаций могут их читать.RetentionPolicy.CLASS
— указывает, что аннотация добавлена в файл класса, но недоступна через отражение.RetentionPolicy.RUNTIME
— аннотации записываются в файл класса компилятором и сохраняются JVM во время выполнения, чтобы их можно было прочитать рефлексивно.
Вот пример кода для создания аннотации, которую можно прочитать во время выполнения:
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {
String value();
}
Теперь аннотации можно получить с помощью отражения:
Description description
= AnnotatedClass.class.getAnnotation(Description.class);
System.out.println(description.value());
Обработчик аннотаций может работать с RetentionPolicy.SOURCE
, это описано в статье Java Annotation Processing and Making a Builder .
RetentionPolicy.CLASS
можно использовать при написании анализатора байт-кода Java.
Q10. Будет ли компилироваться следующий код?
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.FIELD })
public @interface TestAnnotation {
int[] value() default {};
}
Нет. Это ошибка времени компиляции, если одна и та же константа перечисления появляется более одного раза в аннотации @Target
.
Удаление повторяющейся константы приведет к успешной компиляции кода:
@Target({ ElementType.FIELD, ElementType.TYPE})
Q11. Можно ли расширить аннотации?
Нет. Аннотации всегда расширяют java.lang.annotation.Annotation,
как указано в Спецификации языка Java .
Если мы попытаемся использовать предложение extends
в объявлении аннотации, мы получим ошибку компиляции:
public @interface AnAnnotation extends OtherAnnotation {
// Compilation error
}
3. Заключение
В этой статье мы рассмотрели некоторые из часто задаваемых вопросов, возникающих в ходе технических интервью для разработчиков Java, касающихся аннотаций. Это ни в коем случае не исчерпывающий список, и его следует рассматривать только как начало дальнейших исследований.
Мы в ForEach желаем вам успехов в любых предстоящих интервью.