Я занимаюсь онлайн обучением Java и опубликую часть учебных материалов в рамках переработки курса Java Core.
Мой метод обучения состоит в том, что я
- строю усложняющуюся последовательность примеров
- объясняю возможные варианты применения
- объясняю логику двигавшую авторами (по мере возможности)
- даю большое количество тестов (50-100) всесторонне проверяющее понимание и демонстрирующих различные комбинации
- даю лабораторные для самостоятельной работы
Данная статье следует пунктам #1 (последовательность примеров) и #2(варианты применения).
Поехали!
Учебный пример: снабдить классы пользователя мета-информацией о «версии класса».
Итерация #1:
Просто ставим @ перед interface.
public @interface Version {}
Итерация #2:
У аннотаций могут быть атрибуты.
public @interface Version { public int version(); }
И заполнять их при использовании аннотации
@Version(version = 42) public class MyClass {}
Аннотация выше полностью эквивалентна следующей (без public). В этом аннотации эквивалентны интерфейсам: отсутствие модификатора области видимости автоматически означает public (а не package private как у классов).
public @interface Version { int version(); }
С protected и private — не компилируется
public @interface Version { protected int version(); } >> COMPILATION ERROR: Modifier 'protected' not allowed here
Далее я буду использовать вариант без модификатора public
Итерация #3:
Если объявить атрибут с именем value, то его можно опускать при использовании
public @interface Version { public int value(); }
@Version(42) public class MyClass {}
Хотя можно и по старинке
@Version(value = 42) public class MyClass {}
Итерация #4:
Для атрибута можно объявить значения по умолчанию
public @interface Version { int value(); String author() default "UNKNOWN"; }
Теперь у нас два варианта использования. Так
@Version(42) public class MyClass {}
Или вот так
@Version(value = 42, author = "Jim Smith") public class MyClass { }
Но не вот так (слушай, обидно, да)
@Version(42, author = "Jim Smith") public class MyClass {} >> COMPILATION ERROR: Annotation attribute must be of the form 'name=value'
Итерация #5:
Атрибуты могут иметь тип массива
public @interface Author { String[] value() default {}; }
@Author({"Anna", "Mike", "Sara"}) public class MyClass {}
Но только одномерного
public @interface Author2D { String[][] value() default {}; } >> COMPILATION ERROR: Invalid type of annotation member
Итерация #6:
Возможен забавный трюк: аннотация — атрибут аннотации
public @interface Version { int value(); String author() default "UNKNOWN"; }
public @interface History { Version[] value() default {}; }
Применяется вот так
@History({ @Version(1), @Version(value = 2, author = "Jim Smith") }) public class MyClass {}
У аннотаций много ограничений. Перечислим некоторые из них.
Ограничение: тип атрибута
1. Атрибуты могут иметь только следующие типы
- примитивы
- String
- Class или «any parameterized invocation of Class»
- enum
- annotation
- массив элементов любого из вышеперечисленных типов
Последний пункт надо понимать как то, что допустимы только одномерные массивы.
Ну что же, давайте действовать в рамках ограничений
Итерация #7:
В качестве типа атрибута нельзя использовать «обычные» классы Java (за исключением java.lang.String и java.lang.Class), скажем java.util.Date
import java.util.Date; public @interface Version { Date date(); } >> COMPILATION ERROR: Invalid type for annotation member
Но можно эмулировать записи/структуры на аннотациях
public @interface Date { int day(); int month(); int year(); }
public @interface Version { Date date(); }
@Date(year = 2001, month = 1, day = 1) public class MyClass {}
Итерация #8:
Атрибутом аннотации может быть enum. Из приятного, его можно объявить в объявлении аннотации (как и в объявлении интерфейса тут может быть объявление enum, class, interface, annotation)
public @interface Colored { public enum Color {RED, GREEN, BLUE} Color value(); }
import static net.golovach.Colored.Color.RED; @Colored(RED) public class MyClass {}
Итерация #9:
Атрибутом аннотации может быть классовый литерал.
Аннотация версии включает ссылку на предыдущую версию класса.
public @interface Version { int value(); Class<?> previous() default Void.class; }
Первая версия класса
@Version(1) public class ClassVer1 {}
Вторая версия со ссылкой на первую
@Version(value = 2, previous = ClassVer1.class) public class ClassVer2 {}
// Да, я знаю, что нормальные люди не включают версию класса в имя класса. Но знаете как нудно придумывать примеры согласованные с реальной практикой?
Итерация #10:
Менее тривиальный пример с классовым литералом, где я не удержался и добавил generic-ов.
Интерфейс «сериализатора» — того, кто может записать экземпляр T в байтовый поток вывода
import java.io.IOException; import java.io.OutputStream; public interface Serializer<T> { void toStream(T obj, OutputStream out) throws IOException; }
Конкретный «сериализатор» для класса MyClass
import java.io.IOException; import java.io.OutputStream; public class MySerializer implements Serializer<MyClass> { @Override public void toStream(MyClass obj, OutputStream out) throws IOException { throw new UnsupportedOperationException(); } }
Аннотация, при помощи которой мы «приклеиваем сериализатор» к конкретному классу
public @interface SerializedBy { Class<? extends Serializer> value(); }
Ну и сам класс MyClass отмеченный, как сериализуемый «своим сериализатором» MySerializer
@SerializedBy(MySerializer.class) public class MyClass {}
Итерация #11:
Сложный пример
public enum JobTitle { JUNIOR, MIDDLE, SENIOR, LEAD, UNKNOWN }
public @interface Author { String value(); JobTitle title() default JobTitle.UNKNOWN; }
public @interface Date { int day(); int month(); int year(); }
public @interface Version { int version(); Date date(); Author[] authors() default {}; Class<?> previous() default Void.class; }
Ну и наконец использование аннотации
import static net.golovach.JobTitle.*; @History({ @Version( version = 1, date = @Date(year = 2001, month = 1, day = 1)), @Version( version = 2, date = @Date(year = 2002, month = 2, day = 2), authors = {@_8_Author(value = "Jim Smith", title = JUNIOR)}, previous = MyClassVer1.class), @Version( version = 3, date = @Date(year = 2003, month = 3, day = 3), authors = { @Author(value = "Jim Smith", title = MIDDLE), @Author(value = "Anna Lea")}, previous = MyClassVer2.class) }) public class MyClassVer3 {}
Ограничение: значения атрибутов — константы времени компиляции/загрузки JVM
Должна быть возможность вычислить значения атрибутов аннотаций в момент компиляции или загрузки класса в JVM.
public @interface SomeAnnotation { int count(); String name(); }
Пример
@SomeAnnotation( count = 1 + 2, name = MyClass.STR + "Hello" ) public class MyClass { public static final String STR = "ABC"; }
Еще пример
@SomeAnnotation( count = (int) Math.PI, name = "" + Math.PI ) public class MyClass {}
А вот вызовы методов — это уже runtime, это уже запрещено
@SomeAnnotation( count = (int) Math.sin(1), name = "Hello!".toUpperCase() ) public class MyClass {} >> COMPILATION ERROR: Attribute value must be constant
Заключение
Это первая часть статьи про аннотации в Java. Во второй части мы рассмотрим следующие темы:
- Аннотации, модифицирующие поведение других аннотаций: @ Target, @ Retention, @ Documented, @ Inherited
- Аннотации, модифицирующие поведение компилятора и JVM: @ Deprecated, @ Override, @ SafeVarargs, @ SuppressWarnings
- Чтение аннотаций с помощью Reflection API
ссылка на оригинал статьи http://habrahabr.ru/company/golovachcourses/blog/217595/
Добавить комментарий