Кей С. Хорстманн
Скачать 1.62 Mb.
|
public User getUser(@NonNull String userId) Если аннотацию @NonNull можно применять как в параметрах, так и в местах употре- бления типов, то параметр userId аннотируется, а тип параметра обозначается как @NonNull String. Java_SE_9_for_the_Impatient_2nd_Edit.indb 437 16.04.2018 16:40:27 Глава 11 Аннотации 438 11.1.5. Явное указание получателей аннотаций Допустим, что требуется аннотировать параметры, которые не изменяют- ся методом, как показано ниже. public class Point { public boolean equals(@ReadOnly Object other) { ... } } В таком случае инструментальное средство, обрабатывающее данную ан- нотацию, после анализа следующего вызова: p.equals(q) посчитает, что параметр q не изменился. А как насчет ссылки p? При вызове данного метода переменная получателя this привязывается к ссылке p. Но ведь переменная получателя this вообще не объявляется, а следовательно, она и не может быть аннотирована. На самом деле эту переменную можно объявить с помощью редко упо- требляемой разновидности синтаксиса, чтобы ввести аннотацию следующим образом: public class Point { public boolean equals(@ReadOnly Point this, @ReadOnly Object other) { ... } } Первый параметр в приведенном выше примере кода называется параме- тром получателя. Он должен непременно называться this. Его тип относится к тому классу, объект которого создается. НА ЗАМЕТКУ. Параметром получателя можно снабдить только методы, но не конструкто- ры. По существу, ссылка this в конструкторе не является объектом данного типа до тех пор, пока конструктор не завершится. Напротив, аннотация, размещаемая в конструкто- ре, описывает создаваемый объект. Конструктору внутреннего класса передается другой скрытый параметр, а именно: ссылка на объект объемлющего класса. Этот параметр также можно указать явным образом: static class Sequence { private int from; private int to; class Iterator implements java.util.Iterator Java_SE_9_for_the_Impatient_2nd_Edit.indb 438 16.04.2018 16:40:27 439 11.2. Определение аннотаций public Iterator(@ReadOnly Sequence Sequence.this) { this.current = Sequence.this.from; } } } Этот параметр именуется таким же образом, как и при ссылке на него: ОбъемлющийКласс .this. А его тип относится к объемлющему классу. 11.2. Определение аннотаций Каждая аннотация должна быть объявлена в интерфейсе аннотаций с по- мощью синтаксиса @interface. Методы этого интерфейса соответствуют элементам аннотации. Например, аннотация Test модульного теста в JUnit определяется в следующем интерфейсе: @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { long timeout(); } В объявлении @interface создается конкретный интерфейс Java. Инстру- ментальные средства, обрабатывающие аннотации, получают объекты клас- сов, реализующих интерфейс аннотаций. Когда, например, исполнитель текстов в инструментальном средстве JUnit получает объект класса, реализу- ющего интерфейс Test, он просто вызывает метод timeout(), чтобы извлечь элемент установки времени ожидания из конкретной аннотации Test. Аннотации @Target и @Retention являются мета-аннотациями. Они слу- жат аннотациями к аннотации Test, обозначая места, где аннотация может произойти и где она доступна. Значением мета-аннотации @Target служит массив объектов типа ElementType, обозначающих элементы, к которым можно применить аннота- цию. В фигурных скобках можно указать любое количество типов элементов, как показано в следующем примере кода: @Target({ElementType.TYPE, ElementType.METHOD}) public @interface BugReport Все допустимые адресаты аннотаций перечислены в табл. 11.1. Компи- лятор проверяет, применяется ли аннотация только там, где это разреше- но. Так, если аннотация @BugReport применяется к переменной, то во время компиляции возникает ошибка. Java_SE_9_for_the_Impatient_2nd_Edit.indb 439 16.04.2018 16:40:27 Глава 11 Аннотации 440 НА ЗАМЕТКУ. Аннотация без ограничения @Target может употребляться в любых объ- явлениях, но не в параметрах и местах употребления типов. (Эти места были единствен- ными допустимыми адресами аннотаций в первой версии Java, где поддерживались ан- нотации.) В мета-аннотации @Retention указывается место, где аннотация может быть доступна. Этих мест может быть только три, как поясняется ниже. 1. RetentionPolicy.SOURCE. Аннотация доступна для процессов исходно- го кода, но не включается в файлы классов. 2. RetentionPolicy.CLASS. Аннотация включается в файлы классов, но виртуальная машина не загружает их. Этот вариант выбирается по умолчанию. 3. RetentionPolicy.RUNTIME. Аннотация доступна во время выполнения и через прикладной программный интерфейс API для рефлексии. Таблица 11.1. Типы элементов для аннотации @Target Тип элемента Где применяется аннотация ANNOTATION_TYPE Объявления типов аннотаций PACKAGE Пакеты TYPE Классы (включая и перечисления) и интерфейсы (в том числе и типов аннотаций) METHOD Методы CONSTRUCTOR Конструкторы FIELD Переменные экземпляра (включая и константы перечислимого типа) PARAMETER Параметры методов и конструкторов LOCAL_VARIABLE Локальные переменные TYPE_PARAMETER Параметры типов TYPE_USE Места употребления типов Примеры выбора всех трех перечисленных выше мест для доступа к ан- нотациям приведены далее в этой главе. Имеются и другие мета-аннотации, они перечисляются полностью далее, в разделе 11.3. Чтобы задать значение по умолчанию для элемента аннотации, достаточ- но указать оператор default после метода, определяющего этот элемент, как выделено ниже полужирным. public @interface Test { long timeout() default 0L; } Java_SE_9_for_the_Impatient_2nd_Edit.indb 440 16.04.2018 16:40:27 441 11.3. Стандартные аннотации В следующем примере кода демонстрируется, каким образом задается пу- стой массив и значение по умолчанию для аннотации: public @interface BugReport { String[] reportedBy() default {}; // Пустой массив по умолчанию Reference ref() default @Reference(id=0); // Значение по умолчанию для аннотации } ВНИМАНИЕ! Значения по умолчанию не хранятся вместе с аннотацией и вычисляются динамически. Если изменить значение по умолчанию и перекомпилировать аннотиро- ванный класс, во всех аннотированных его элементах будет использовано новое зна- чение по умолчанию, даже если файлы классов компилировались до изменения этого значения. Интерфейсы аннотаций расширению не подлежат. Это означает, что для реализации интерфейсов нельзя предоставить конкретные классы. Вместо этого инструментальные средства обработки исходного кода и виртуальная машина генерируют классы и объекты-заместители по мере надобности. 11.3. Стандартные аннотации В пакетах java.lang, java.lang.annotation и javax.annotation из при- кладного программного интерфейса Java API определяется целый ряд интер- фейсов аннотаций. Четыре из них относятся к мета-аннотациям, описываю- щим поведение интерфейсов аннотаций, а другие — к обычным аннотациям, служащим для аннотирования отдельных элементов в исходном коде. Все эти разновидности аннотаций перечислены в табл. 11.2, а подробнее они рассма- триваются в двух последующих разделах. Таблица 11.2. Стандартные аннотации Интерфейс аннотаций Где применяется Назначение Override Методы Проверяет, переопределяет ли данный метод соответствующий метод из суперкласса Deprecated Все объявления Помечает элемент кода как не рекомендован- ный к употреблению SuppressWarnings Все объявления, кроме пакетов Подавляет предупреждения данного типа SafeVarargs Методы и конструкторы Утверждает, что пользоваться аргументами пе- ременной длины безопасно FunctionalInterface Интерфейсы Помечает интерфейс как функциональный с единственным абстрактным методом Java_SE_9_for_the_Impatient_2nd_Edit.indb 441 16.04.2018 16:40:27 Глава 11 Аннотации 442 Интерфейс аннотаций Где применяется Назначение PostConstruct Методы Метод должен быть вызван сразу же после создания PreDestroy или до удаления внедряемого объекта Resource Классы и интерфейсы, методы, поля Класс и интерфейс помечаются как ресурс, ис- пользуемый повсеместно, а метод или поле — для внедрения зависимостей Resources Классы и интерфейсы Обозначает массив ресурсов Generated Все объявления Помечает элемент исходного кода как сформи- рованный инструментальным средством Target Аннотации Обозначает места, где может быть применена данная аннотация Retention Аннотации Обозначает места, где может быть применена данная аннотация Documented Аннотации Обозначает, что данная аннотация должна быть включена в документацию на аннотиро- ванные элементы кода Inherited Аннотации Обозначает, что данная аннотация наследуется подклассом Repeatable Аннотации Обозначает, что данную аннотацию можно применить несколько раз к одному и тому же элементу кода 11.3.1. Аннотации для компиляции Аннотация @Deprecated может быть присоединена к любым элемен- там кода, применение которых больше не поощряется. Компилятор выдаст предупреждение, если в исходном коде будет обнаружен элемент, не реко- мендованный к употреблению. Эта аннотация имеет то же назначение, что и дескриптор @deprecated документирующей документации. Тем не менее аннотация сохраняется вплоть до времени выполнения. НА ЗАМЕТКУ. В комплект JDK входит утилита jdeprscan, способная просмотреть не рекомендуемые к применению элементы в архивных JAR-файлах. Аннотация @Override вынуждает компилятор проверять, что аннотируе- мый метод действительно переопределяет метод из суперкласса. Так, если объявляется следующий класс: public class Point { @Override public boolean equals(Point other) { ... } } Окончание табл. 11.2 Java_SE_9_for_the_Impatient_2nd_Edit.indb 442 16.04.2018 16:40:27 443 11.3. Стандартные аннотации то компилятор известит об ошибке в связи с тем, что метод equals() не пе- реопределяет одноименный метод equals() из класса Object, поскольку па- раметр этого метода относится к типу Object, а не к типу Point. Аннотация @SuppressWarnings дает компилятору команду подавить пред- упреждения конкретного типа, как показано в следующем примере кода: @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cl, n); Аннотация @SafeVarargs утверждает, что метод не нарушает свой пара- метр переменной длины (см. главу 6). Аннотация @Generated предназначена для применения в инструменталь- ных средствах генерирования кода. Любой генерируемый исходный код мо- жет быть аннотирован, чтобы отличать его от кода, написанного вручную. Например, в редакторе исходного текста можно скрыть генерируемый код, а в генераторе кода — удалить прежние версии генерируемого кода. Каждая аннотация должна содержать однозначный идентификатор генератора кода. Дополнительную строку с датой (в формате по стандарту ISO 8601) и строку комментариев указывать можно, но не обязательно: @Generated(value="com.horstmann.generator", date="2015-01-04T12:08:56.235-0700"); В главе 3 был приведен пример употребления аннотации @Functional Interface. Она аннотирует преобразование адресатов лямбда-выражений, как показано ниже. Если в дальнейшем ввести в данный интерфейс еще один абстрактный метод, компилятор выдаст ошибку. @FunctionalInterface public interface IntFunction R apply(int value); } Разумеется, такие аннотации должны быть введены в интерфейсы, описы- вающие отдельные функции. Имеются и другие интерфейсы с единственным абстрактным методом (например, интерфейс AutoCloseable), по существу, не являющиеся функциями. 11.3.2. Аннотации для управления ресурсами Аннотации @PostConstruct и @PreDestroy применяются в средах, управ- ляющих сроком действия объектов, например, в веб-контейнерах и серверах приложений. Методы, помеченные этими аннотациями, должны вызываться сразу же после создания объекта или непосредственно перед его удалением. Аннотация @Resource предназначена для внедрения ресурсов. В каче- стве примера рассмотрим веб-приложение, осуществляющее доступ к базе Java_SE_9_for_the_Impatient_2nd_Edit.indb 443 16.04.2018 16:40:27 Глава 11 Аннотации 444 данных. Безусловно, доступ к информации в базе данных не должен быть жестко закодирован в данном веб-приложении. Вместо этого у веб-контей- нера имеется свой пользовательский интерфейс для установки параметров подключения к базе данных, а также имя источника данных, определяемое в прикладном программном интерфейсе JNDI. Обращаться к этому источнику данных из веб-приложения можно следующим образом: @Resource(name="jdbc/employeedb") private DataSource source; Когда создается объект, содержащий приведенную выше переменную эк- земпляра, веб-контейнер внедряет ссылку на источник данных. Это означает, что он устанавливает в переменной экземпляра объект типа DataSource, на- страиваемый на имя "jdbc/employeedb". 11.3.3. Мета-аннотации Мета-аннотации @Target и @Retention упоминались в разделе 11.2. А ме- та-документация @Documented предоставляет указание для инструментальных средств документирования вроде утилиты javadoc. Документируемые анно- тации следует рассматривать как разновидность модификаторов (например, private или static), употребляемых для документирования. Остальные ан- нотации не следует включать в документацию. Например, аннотация @SuppressWarnings не документируется. Если метод или поле содержит такую аннотацию, то особенности ее реализации мало- интересны читающему документацию на прикладной код. С другой стороны, аннотация @FunctionalInterface документируется, поскольку программи- сту важно знать, что аннотируемый ею интерфейс предназначен для описа- ния функции. Пример документируемой аннотации приведен на рис. 11.1. Мета-аннотация @Inherited применяется только к аннотациям классов. Если в классе имеется наследуемая аннотация, то все его подклассы автома- тически получают ту же самую аннотацию. Благодаря этому упрощается со- здание аннотаций, действующих аналогично маркерным интерфейсам (на- пример, интерфейсу Serializable). Допустим, аннотация @Persistent определяется с целью определить, что объекты класса могут быть сохранены в базе данных. В таком случае подклас- сы постоянных классов автоматически аннотируются как постоянные. @Inherited @interface Persistent { } @Persistent class Employee { . . . } class Manager extends Employee { . . . } // Этот класс также имеет аннотацию @Persistent Java_SE_9_for_the_Impatient_2nd_Edit.indb 444 16.04.2018 16:40:27 445 11.3. Стандартные аннотации Рис. 11.1. Документируемая аннотация Мета-аннотация @Repeatable позволяет применить одну и ту же аннота- цию неоднократно. Допустим, что аннотация @TestCase повторяется. В та- ком случае ею можно воспользоваться следующим образом: @TestCase(params="4", expected="24") @TestCase(params="0", expected="1") public static long factorial(int n) { ... } По ряду исторических причин разработчикам повторяющейся аннотации пришлось предоставить контейнерную аннотацию, содержащую повторяю- щиеся аннотации в массиве. Ниже показано, каким образом определяется аннотация @TestCase и ее контейнер. @Repeatable(TestCases.class) @interface TestCase { String params(); String expected(); } @interface TestCases { TestCase[] value(); } Java_SE_9_for_the_Impatient_2nd_Edit.indb 445 16.04.2018 16:40:27 Глава 11 Аннотации 446 Всякий раз, когда пользователь предоставляет две или больше аннота- ции @TestCase, они автоматически заключаются в оболочку аннотации @TestCases. Это усложняет обработку аннотации, как будет показано в сле- дующем разделе. 11.4. Обработка аннотаций во время выполнения В приведенных до сих пор примерах было показано, каким образом анно- тации вводятся в исходные файлы и как определяются типы аннотаций. А те- перь настало время выяснить, какую же пользу можно извлечь из аннотаций. В этом разделе на простом примере поясняется обработка аннотаций во время выполнения с использованием прикладного программного интерфей- са API для рефлексии, рассматривавшегося в главе 4. Допустим, требуется сократить затраты труда на реализацию методов типа toString. Можно, конечно, написать обобщенный метод toString(), используя рефлексию, чтобы учесть имена и значения всех переменных экземпляра. Но допустим, что этот процесс требуется специально настроить, чтобы не включать в него все переменные экземпляра или пропустить имена классов и переменных. Например, для класса Point более предпочтительной может оказаться обо- значение координат точки [5,10] вместо обозначения Point[x=5,y=10]. Раз- умеется, в данный процесс можно внести и другие усовершенствования, но мы не будем этого делать ради простоты примера. Самое главное — проде- монстрировать в нем возможности процессора аннотаций. Все классы, в которых требуется извлечь выгоду из данного процесса, сле- дует снабдить аннотацией @ToString. Аннотировать следует и все перемен- ные экземпляра, которые должны быть включены в данный процесс. Аннота- ция @ToString определяется следующим образом: @Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ToString { boolean includeName() default true; } Ниже приведены аннотированные классы Point и Rectangle. При этом преследуется цель представить прямоугольник в виде символьной строки с параметрами Rectangle[[5, 10], width=20,height=30]. @ToString(includeName=false) public class Point { @ToString(includeName=false) private int x; @ToString(includeName=false) private int y; } Java_SE_9_for_the_Impatient_2nd_Edit.indb 446 16.04.2018 16:40:27 |