Глава 9. Интерфейсы
Содержание
Объявление интерфейса вводит новый ссылочный тип, членами которого являются классы, интерфейсы, константы и абстрактные методы. Этот тип не имеет реализации, но иначе не связанные классы могут реализовать его, предоставив реализации его абстрактных методов.
A вложенный интерфейс — это любой интерфейс, объявление которого находится в теле другого класса или интерфейса.
A интерфейс верхнего уровня — это интерфейс, который не является вложенным интерфейсом.
Мы различаем два вида интерфейсов — нормальные интерфейсы и типы аннотаций.
В этой главе обсуждается общая семантика всех интерфейсов — нормальных интерфейсов, как верхнего уровня (§7.6), так и вложенных (§8.5, §9.5), и типов аннотаций (§9.6). Подробности, относящиеся к конкретным видам интерфейсов, обсуждаются в разделах, посвященных этим конструкциям.
Программы могут использовать интерфейсы, чтобы сделать ненужным, чтобы связанные классы имели общий abstract суперкласс или добавляли методы к Object.
Интерфейс может быть объявлен как прямое расширение одного или нескольких других интерфейсов, что означает, что он неявно указывает все типы членов, абстрактные методы и константы интерфейсов, которые он расширяет, за исключением любых типов членов и констант, которые он может скрыть.
Класс может быть объявлен как непосредственно реализующий один или несколько интерфейсов, что означает, что любой экземпляр класса реализует все abstract методы, указанные интерфейсом или интерфейсами. Класс обязательно реализует все интерфейсы, которые реализуют его прямые суперклассы и прямые суперинтерфейсы. Это (множественное) наследование интерфейсов позволяет объектам поддерживать (множественное) общее поведение без общего использования какой-либо реализации.
Переменная, объявленный тип которой является типом интерфейса, может иметь в качестве значения ссылку на любой экземпляр класса, который реализует указанный интерфейс. Недостаточно, чтобы класс случайно реализовал все abstract методы интерфейса; класс или один из его суперклассов должны быть объявлены для реализации интерфейса, иначе класс не считается реализующим интерфейс.
Объявление интерфейса определяет новый именованный ссылочный тип. Существует два вида объявлений интерфейсов — обычные объявления интерфейсов и объявления типов аннотаций (§9.6).
InterfaceDeclaration:
NormalInterfaceDeclaration
AnnotationTypeDeclaration
NormalInterfaceDeclaration:
InterfaceModifiersopt interface Identifier
TypeParametersopt ExtendsInterfacesopt InterfaceBody
Идентификатор Identifier в объявлении интерфейса определяет имя интерфейса.
Ошибка времени компиляции, если интерфейс имеет такое же простое имя, как и любой из его включающих классов или интерфейсов.
Область видимости и сокрытие объявления интерфейса указаны в §6.3 и §6.4.
Объявление интерфейса может включать модификаторы интерфейса.
InterfaceModifiers:
InterfaceModifier
InterfaceModifiers InterfaceModifier
InterfaceModifier: one of
Annotation public protected private
abstract static strictfp
Если аннотация a (§9.7) в объявлении интерфейса соответствует типу аннотации T (§9.6), а у T есть (мета-)аннотация m, которая соответствует java.lang.annotation.Target, то m должен иметь элемент, значение которого равно java.lang.annotation.ElementType.TYPE, иначе возникает ошибка времени компиляции.
Модификатор доступа public (§6.6) относится к каждому виду объявления интерфейса.
Модификаторы доступа protected и private относятся только к интерфейсам-членам внутри непосредственно включающего объявления класса или перечисления (§8.5.1).
Модификатор static относится только к интерфейсам-членам (§8.5.1, §9.5), а не к интерфейсам верхнего уровня (§7.6).
Ошибка времени компиляции, если один и тот же модификатор встречается более одного раза в объявлении интерфейса.
Если в объявлении интерфейса появляется два или более (отдельных) модификатора интерфейса, то принято, хотя и не обязательно, чтобы они отображались в порядке, соответствующем указанному выше в производстве для InterfaceModifier.
Каждый интерфейс неявно является abstract.
Этот модификатор устарел и не должен использоваться в новых программах.
Эффект модификатора strictfp заключается в том, чтобы сделать все выражения float или double в объявлении интерфейса явно FP-строгими (§15.4).
Это подразумевает, что все вложенные типы, объявленные в интерфейсе, неявно являются strictfp.
Интерфейс является обобщенным, если он объявляет одну или несколько переменных типа (§4.4).
Эти переменные типа известны как параметры типа интерфейса. Раздел параметров типа следует за именем интерфейса и отделяется угловыми скобками.
В разделе параметров типа интерфейса переменная типа T непосредственно зависит от переменной типа S, если S является границей T, а T зависит от S, если либо T непосредственно зависит от S, либо T непосредственно зависит от переменной типа U, которая зависит от S (с использованием этого определения рекурсивно).
Ошибка времени компиляции, если переменная типа в разделе параметров типа интерфейса зависит от самой себя.
Область видимости параметра типа интерфейса указана в §6.3.
Ошибка времени компиляции при ссылке на параметр типа интерфейса I где-либо в объявлении поля или члена типа I.
Объявление обобщенного интерфейса определяет набор параметризованных типов (§4.5), по одному для каждого возможного вызова раздела параметров типа аргументами типа. Все эти параметризованные типы совместно используют один и тот же интерфейс во время выполнения.
Если предоставлено предложение extends, то объявляемый интерфейс расширяет каждый из других именованных интерфейсов и, следовательно, наследует типы-члены, методы и константы каждого из других именованных интерфейсов.
Эти другие именованные интерфейсы являются прямыми суперинтерфейсами объявляемого интерфейса.
Любой класс, который implements объявленный интерфейс, также считается реализующим все интерфейсы, которые этот интерфейс extends.
ExtendsInterfaces:
extends InterfaceTypeList
Следующее повторяется из §4.3 и §8.1.5, чтобы сделать представление здесь более понятным:
InterfaceTypeList:
InterfaceType
InterfaceTypeList , InterfaceType
InterfaceType:
TypeDeclSpecifier TypeArgumentsopt
Учитывая (возможно, общий) объявление интерфейса для I<F1,...,Fn> (n ≥ 0), прямые суперинтерфейсы типа интерфейса I<F1,...,Fn> — это типы, указанные в предложении extends объявления I, если присутствует предложение extends.
Пусть I<F1,...,Fn> (n > 0), будет общим объявлением интерфейса. Прямые суперинтерфейсы параметризованного типа интерфейса I<T1,...,Tn>, где Ti (1 ≤ i ≤ n) является типом, — все типы J<U1 θ,...,Uk θ>, где J<U1,...,Uk> — прямой суперинтерфейс I<F1,...,Fn>, а θ — подстановка [F1:=T1,...,Fn:=Tn].
Каждый InterfaceType в предложении extends объявления интерфейса должен именовать доступный (§6.6) тип интерфейса; в противном случае возникает ошибка во время компиляции.
Отношение superinterface — это транзитивное замыкание отношения прямого суперинтерфейса. Интерфейс K является суперинтерфейсом интерфейса I, если выполняется одно из следующих условий:
-
K является прямым суперинтерфейсом I.
-
Существует интерфейс J, такой, что K является суперинтерфейсом J, а J является суперинтерфейсом I, применяя это определение рекурсивно.
Интерфейс I называется субинтерфейсом интерфейса K всякий раз, когда K является суперинтерфейсом I.
Хотя каждый класс является расширением класса Object, не существует ни одного интерфейса, расширениями которого являются все интерфейсы.
Интерфейс I непосредственно зависит от типа T, если T упоминается в предложении extends I либо как суперинтерфейс, либо как квалификатор в имени суперинтерфейса.
Интерфейс I зависит от ссылочного типа T, если выполняется какое-либо из следующих условий:
-
I напрямую зависит от T.
-
I напрямую зависит от класса C, который зависит (§8.1.5) от T.
-
I напрямую зависит от интерфейса J, который зависит от T (используя это определение рекурсивно).
Ошибка во время компиляции, если интерфейс зависит от самого себя.
Если циклически объявленные интерфейсы обнаруживаются во время выполнения, когда интерфейсы загружаются (§12.2), то возникает ClassCircularityError.
Тело интерфейса может объявлять члены интерфейса, то есть поля (§9.3), методы (§9.4), классы (§9.5) и интерфейсы (§9.5).
InterfaceBody:
{ InterfaceMemberDeclarationsopt }
InterfaceMemberDeclarations:
InterfaceMemberDeclaration
InterfaceMemberDeclarations InterfaceMemberDeclaration
InterfaceMemberDeclaration:
ConstantDeclaration
AbstractMethodDeclaration
ClassDeclaration
InterfaceDeclaration
;
Область видимости объявления члена m, объявленного в или унаследованного типом интерфейса I, указана в §6.3.
Членами интерфейса являются:
-
Члены, объявленные в интерфейсе.
-
Члены, унаследованные от прямых суперинтерфейсов.
-
Если интерфейс не имеет прямых суперинтерфейсов, то интерфейс неявно объявляет
publicabstractчлен методmс сигнатурой s, типом возвращаемого значения r и предложениемthrowst, соответствующим каждомуpublicметоду экземпляраmс сигнатурой s, типом возвращаемого значения r и предложениемthrowst, объявленному вObject, если только интерфейс явно не объявляет метод с той же сигнатурой, тем же типом возвращаемого значения и совместимым предложениемthrows.Ошибка во время компиляции, если интерфейс явно объявляет такой метод
mв том случае, когдаmобъявляется какfinalвObject.Из этого следует, что ошибка во время компиляции, если интерфейс объявляет метод с сигнатурой, которая эквивалентна переопределению (§8.4.2) методу
publicObject, но имеет другой тип возвращаемого значения или несовместимое предложениеthrows.
Интерфейс наследует от интерфейсов, которые он расширяет, все члены этих интерфейсов, за исключением (a) полей, классов и интерфейсов, которые он скрывает, и (b) методов, которые он переопределяет (§9.4.1).
Поля, методы и типы членов типа интерфейса могут иметь одинаковое имя, поскольку они используются в разных контекстах и различаются разными процедурами поиска (§6.5). Однако это не рекомендуется в отношении стиля.
ConstantDeclaration:
ConstantModifiersopt Type VariableDeclarators ;
ConstantModifiers:
ConstantModifier
ConstantModifier ConstantModifers
ConstantModifier: one of
Annotation public static final
Если аннотация a ( §9.7) в объявлении поля соответствует типу аннотации T ( §9.6), и T имеет (мета-)аннотацию m, которая соответствует java.lang.annotation.Target, то m должен иметь элемент, значение которого java.lang.annotation.ElementType.FIELD, иначе возникает ошибка времени компиляции.
Каждое объявление поля в теле интерфейса неявно public, static и final. Разрешено избыточно указывать любые или все эти модификаторы для таких полей.
Если два или более (различных) модификатора поля появляются в объявлении поля, принято, хотя и не обязательно, чтобы они появлялись в порядке, соответствующем показанному выше в продукции для ConstantModifier.
Это ошибка времени компиляции, если один и тот же модификатор появляется в объявлении поля более одного раза.
Это ошибка времени компиляции для тела объявления интерфейса объявить два поля с одинаковым именем.
Объявленный тип поля обозначается Type, который появляется в объявлении поля, за которым следуют любые пары скобок, следующие за Identifier в дескрипторе.
Если интерфейс объявляет поле с определенным именем, то объявление этого поля, как говорят, hide любые и все доступные объявления полей с тем же именем в суперинтерфейсах интерфейса.
Интерфейс может наследовать более одного поля с одним и тем же именем. Такая ситуация сама по себе не вызывает ошибки времени компиляции. Однако любая попытка в теле интерфейса сослаться на такое поле по его простому имени приведет к ошибке времени компиляции, потому что такая ссылка неоднозначна.
Может быть несколько путей, по которым может быть унаследовано одно и то же объявление поля из интерфейса. В такой ситуации поле считается унаследованным только один раз, и на него можно ссылаться по его простому имени без неоднозначности.
Example 9.3-1. Ambiguous Inherited Fields
Если два поля с одним и тем же именем наследуются интерфейсом, потому что, например, два его прямых суперинтерфейса объявляют поля с этим именем, то результатом является один неоднозначный член. Любое использование этого неоднозначного члена приведет к ошибке времени компиляции. В программе:
interface BaseColors {
int RED = 1, GREEN = 2, BLUE = 4;
}
interface RainbowColors extends BaseColors {
int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7;
}
interface PrintColors extends BaseColors {
int YELLOW = 8, CYAN = 16, MAGENTA = 32;
}
interface LotsOfColors extends RainbowColors, PrintColors {
int FUCHSIA = 17, VERMILION = 43, CHARTREUSE = RED+90;
}
интерфейс LotsOfColors наследует два поля с именем YELLOW. Это нормально до тех пор, пока интерфейс не содержит никаких ссылок по простому имени на поле YELLOW. (Такая ссылка может возникнуть в инициализаторе переменной для поля.)
Даже если интерфейс PrintColors должен был бы дать значение 3 to YELLOW, а не значение 8, ссылка на поле YELLOW в интерфейсе LotsOfColors все равно будет считаться неоднозначной.
Example 9.3-2. Multiply Inherited Fields
Если одно поле наследуется несколько раз из одного и того же интерфейса, потому что, например, и этот интерфейс, и один из прямых суперинтерфейсов этого интерфейса расширяют интерфейс, который объявляет поле, то результатом является только один член. Эта ситуация сама по себе не вызывает ошибки времени компиляции.
В предыдущем примере поля RED, GREEN и BLUE наследуются интерфейсом LotsOfColors более чем одним способом, через интерфейс RainbowColors, а также через интерфейс PrintColors, но ссылка на поле RED в интерфейсе LotsOfColors не считается неоднозначной, потому что задействовано только одно фактическое объявление поля RED.
Каждое поле в теле интерфейса должно иметь выражение инициализации, которое не обязательно должно быть константным выражением ( §15.28), иначе возникает ошибка времени компиляции.
Это ошибка времени компиляции, если выражение инициализации для поля интерфейса содержит ссылку по простому имени на то же поле или на другое поле, чье объявление встречается текстуально позже в том же интерфейсе.
Example 9.3.1-1. Forward Reference to a Field
interface Test {
float f = j;
int j = 1;
int k = k + 1;
}
Эта программа вызывает две ошибки времени компиляции, потому что j упоминается при инициализации f до того, как j объявлено, и потому, что инициализация k относится к k самому.
Если ключевое слово this ( §15.8.3) или ключевое слово super ( §15.11.2, §15.12) встречается в выражении инициализации для поля интерфейса, то, если только это в теле анонимного класса ( §15.9.5), возникает ошибка времени компиляции.
Инициализатор переменной вычисляется, и присваивание выполняется ровно один раз, когда интерфейс инициализируется ( §12.4.2).
Во время выполнения поля интерфейса, которые инициализируются константными выражениями ( §15.28), инициализируются первыми ( §12.4.2). Это также относится к static final полям в классах ( §8.3.2.1). Эти поля являются "константами", которые никогда не будут замечены с их значениями по умолчанию ( §4.12.5), даже коварными программами ( §13.4.9).
AbstractMethodDeclaration:
AbstractMethodModifiersopt TypeParametersopt Result
MethodDeclarator Throwsopt ;
AbstractMethodModifiers:
AbstractMethodModifier
AbstractMethodModifiers AbstractMethodModifier
AbstractMethodModifier: one of
Annotation public abstract
Если аннотация a (§9.7) в объявлении метода соответствует типу аннотации T (§9.6), и у T есть (мета-)аннотация m, которая соответствует java.lang.annotation.Target, тогда m должен иметь элемент, значение которого равно java.lang.annotation.ElementType.METHOD, или возникает ошибка времени компиляции.
Каждое объявление метода в теле интерфейса неявно является public (§6.6).
Каждое объявление метода в теле интерфейса неявно является abstract, поэтому его тело всегда представляется точкой с запятой, а не блоком.
Разрешено, но не рекомендуется в качестве стиля, избыточно указывать модификатор public и/или abstract для метода, объявленного в интерфейсе.
Это ошибка времени компиляции, если один и тот же модификатор появляется более одного раза в методе, объявленном в интерфейсе.
Это ошибка времени компиляции, если метод, объявленный в интерфейсе, объявлен static, потому что static методы не могут быть abstract.
Это ошибка времени компиляции, если метод, объявленный в интерфейсе, является strictfp или native или synchronized, потому что эти ключевые слова описывают свойства реализации, а не свойства интерфейса.
Однако метод, объявленный в интерфейсе, может быть реализован методом, который объявлен strictfp или native или synchronized в классе, реализующем интерфейс.
Это ошибка времени компиляции, если метод, объявленный в интерфейсе, объявлен final.
Однако метод, объявленный в интерфейсе, может быть реализован методом, который объявлен final в классе, реализующем интерфейс.
Это ошибка времени компиляции, если тело интерфейса явно или неявно объявляет два метода с эквивалентными сигнатурами переопределения (§8.4.2).
Однако интерфейс может наследовать несколько методов с такими сигнатурами (§9.4.1).
Метод в интерфейсе может быть обобщенным. Правила для параметров типа обобщенного метода в интерфейсе такие же, как и для обобщенного метода в классе (§8.4.4).
Интерфейс наследует от своих прямых суперинтерфейсов все методы суперинтерфейсов, которые не переопределены объявлением в интерфейсе.
Методы переопределяются по принципу сигнатура-за-сигнатурой.
Например, если интерфейс объявляет два public метода с одинаковым именем (§9.4.2), и под-интерфейс переопределяет один из них, под-интерфейс все равно наследует другой метод.
Метод экземпляра m1, объявленный в интерфейсе I, переопределяет другой метод экземпляра, m2, объявленный в интерфейсе J, если выполняются оба следующих условия:
-
I является подинтерфейсом J.
-
Сигнатура
m1является подсигнатурой (§8.4.2) сигнатурыm2.
Пример 9.4.1.1-1. Переопределение объявления метода abstract
Методы, объявленные в интерфейсах, являются abstract и, следовательно, не содержат реализации. Все, что можно сделать с помощью объявления переопределяющего метода, помимо подтверждения сигнатуры метода, — это уточнить тип возвращаемого значения или ограничить исключения, которые могут быть вызваны реализацией метода. Вот вариант примера, показанного в (§8.4.3.1):
class BufferEmpty extends Exception {}
class BufferException extends Exception {}
interface Buffer {
char get() throws BufferEmpty, BufferException;
}
interface InfiniteBuffer extends Buffer {
char get() throws BufferException; // override
}
Взаимосвязь между типом возвращаемого значения метода интерфейса и типами возвращаемых значений любых переопределенных методов интерфейса указана в §8.4.8.3.
Взаимосвязь между предложением throws метода интерфейса и предложениями throws любых переопределенных методов интерфейса указана в §8.4.8.3.
Взаимосвязь между сигнатурой метода интерфейса и сигнатурами переопределенных методов интерфейса указана в §8.4.8.3.
Интерфейс может наследовать несколько методов с эквивалентными сигнатурами переопределения (§8.4.2). Такая ситуация сама по себе не вызывает ошибку времени компиляции. Считается, что интерфейс наследует все методы.
Однако один из унаследованных методов должен быть взаимозаменяемым по типу возвращаемого значения для каждого другого унаследованного метода; в противном случае возникает ошибка времени компиляции. (Предложения throws не вызывают ошибок в этом случае.)
Может быть несколько путей, по которым одно и то же объявление метода наследуется от интерфейса. Этот факт не вызывает затруднений и никогда сам по себе не приводит к ошибке времени компиляции.
Если два метода интерфейса (будь то оба объявлены в одном и том же интерфейсе, или оба унаследованы интерфейсом, или один объявлен, а другой унаследован) имеют одно и то же имя, но разные сигнатуры, которые не являются эквивалентными переопределению (§8.4.2), то имя метода считается перегруженным.
Этот факт не вызывает затруднений и сам по себе никогда не приводит к ошибке времени компиляции. Нет никакой требуемой взаимосвязи между типами возвращаемого значения или между предложениями throws двух методов с одинаковым именем, но разными сигнатурами, которые не эквивалентны переопределению.
Пример 9.4.2-1. Перегрузка объявления метода abstract
interface PointInterface {
void move(int dx, int dy);
}
interface RealPointInterface extends PointInterface {
void move(float dx, float dy);
void move(double dx, double dy);
}
Здесь метод с именем move перегружен в интерфейсе RealPointInterface с тремя разными сигнатурами, две из которых объявлены и одна унаследована. Любой не-abstract класс, реализующий интерфейс RealPointInterface, должен предоставить реализации всех трех сигнатур методов.
Интерфейсы могут содержать объявления типов-членов (§8.5).
Объявление типа-члена в интерфейсе неявно является static и public. Разрешено избыточно указывать любой из этих модификаторов или оба.
Это ошибка времени компиляции, если один и тот же модификатор появляется более одного раза в объявлении типа-члена в интерфейсе.
Если интерфейс объявляет тип-член с определенным именем, то объявление этого типа, как говорят, скрывает любые и все доступные объявления типов-членов с тем же именем в суперинтерфейсах интерфейса.
Интерфейс наследует от своих прямых суперинтерфейсов все не-private типы-члены суперинтерфейсов, которые доступны коду в интерфейсе и не скрыты объявлением в интерфейсе.
Интерфейс может наследовать два или более объявления типов с одним и тем же именем. Это ошибка времени компиляции, если вы попытаетесь обратиться к любому неоднозначно унаследованному классу или интерфейсу по его простому имени.
Если одно и то же объявление типа наследуется от интерфейса по нескольким путям, класс или интерфейс считается унаследованным только один раз; к нему можно обращаться по его простому имени без неоднозначности.
Объявление типа аннотации — это особый вид объявления интерфейса. Чтобы отличить объявление типа аннотации от обычного объявления интерфейса, ключевое слово interface предшествует знаку @ (@).
AnnotationTypeDeclaration:
InterfaceModifiersopt @ interface Identifier AnnotationTypeBody
AnnotationTypeBody:
{ AnnotationTypeElementDeclarationsopt }
AnnotationTypeElementDeclarations:
AnnotationTypeElementDeclaration
AnnotationTypeElementDeclarations AnnotationTypeElementDeclaration
Обратите внимание, что знак @ (@) и ключевое слово interface — это два разных токена. Технически их можно разделить пробелом, но это не рекомендуется как вопрос стиля.
Если аннотация a (§9.7) в объявлении типа аннотации соответствует типу аннотации T, и T имеет (мета-)аннотацию m, которая соответствует java.lang.annotation.Target, то m должен иметь либо элемент, значение которого равно java.lang.annotation.ElementType.ANNOTATION_TYPE, либо элемент, значение которого равно java.lang.annotation.ElementType.TYPE, в противном случае возникает ошибка времени компиляции.
Идентификатор Identifier в объявлении типа аннотации указывает имя типа аннотации.
Это ошибка времени компиляции, если тип аннотации имеет то же простое имя, что и любой из его включающих классов или интерфейсов.
Непосредственным супер-интерфейсом типа аннотации всегда является java.lang.annotation.Annotation.
В силу синтаксиса AnnotationTypeDeclaration объявление типа аннотации не может быть общим, и предложение extends не допускается.
Следствием того факта, что тип аннотации не может явно объявить суперкласс или суперинтерфейс, является то, что подкласс или подинтерфейс типа аннотации никогда не является сам типом аннотации. Аналогично, java.lang.annotation.Annotation не является сам типом аннотации.
Объявление типа аннотации наследует несколько членов от java.lang.annotation.Annotation, включая неявно объявленные методы, соответствующие методам экземпляра в Object, однако эти методы не определяют элементы (§9.6.1) типа аннотации, и их использование в аннотациях запрещено.
Без этого правила мы не смогли бы гарантировать, что элементы будут иметь типы, представимые в аннотациях, или что методы доступа к ним будут доступны.
Если иное явно не указано в данном документе, все правила, применимые к обычным объявлениям интерфейсов, применимы к объявлениям типов аннотаций.
Например, типы аннотаций используют то же пространство имен, что и обычные типы классов и интерфейсов; объявления типов аннотаций допустимы везде, где допустимы объявления интерфейсов, и имеют ту же область видимости и доступность.
Каждое объявление метода в объявлении типа аннотации определяет элемент типа аннотации.
Типы аннотаций могут иметь ноль или более элементов. Тип аннотации не имеет элементов, отличных от тех, которые определены методами, которые он явно объявляет.
AnnotationTypeElementDeclaration:
AbstractMethodModifiersopt Type Identifier ( ) Dimsopt DefaultValueopt ;
ConstantDeclaration
ClassDeclaration
InterfaceDeclaration
EnumDeclaration
AnnotationTypeDeclaration
;
DefaultValue:
default ElementValue
В силу синтаксиса AnnotationTypeElementDeclaration объявление метода в объявлении типа аннотации не может иметь формальных параметров или параметров типа, или предложения throws.
По соглашению, никакие AbstractMethodModifiers не должны присутствовать в элементе типа аннотации, кроме аннотаций.
Пример 9.6.1-1. Объявления типов аннотаций
Следующее объявление типа аннотации определяет тип аннотации с несколькими элементами:
/**
* Describes the "request-for-enhancement" (RFE)
* that led to the presence of the annotated API element.
*/
@interface RequestForEnhancement {
int id(); // Unique ID number associated with RFE
String synopsis(); // Synopsis of RFE
String engineer(); // Name of engineer who implemented RFE
String date(); // Date RFE was implemented
}
Следующее объявление типа аннотации определяет тип аннотации без элементов, называемый типом аннотации-маркера:
/**
* An annotation with this type indicates that the
* specification of the annotated API element is
* preliminary and subject to change.
*/
@interface Preliminary {}
Это ошибка времени компиляции, если тип возвращаемого значения метода, объявленного в типе аннотации, не является одним из следующих: примитивный тип, String, Class, любое параметризованное обращение к Class, тип enum (§8.9), тип аннотации или тип массива (§10), типом элемента которого является один из предыдущих типов.
Эта спецификация исключает элементы, типы которых являются вложенными массивами. Например, это объявление типа аннотации является недопустимым:
@interface Verboten {
String[][] value();
}
Это ошибка времени компиляции, если какой-либо метод, объявленный в типе аннотации, имеет сигнатуру, эквивалентную переопределению, любому методу public или protected, объявленному в классе Object или в интерфейсе java.lang.annotation.Annotation.
Это ошибка времени компиляции, если объявление типа аннотации T содержит элемент типа T, либо непосредственно, либо косвенно.
Например, это недопустимо:
@interface SelfRef { SelfRef value(); }
и это тоже:
@interface Ping { Pong value(); }
@interface Pong { Ping value(); }
По соглашению, имя единственного элемента в одноэлементном типе аннотации — value. Лингвистическая поддержка этого соглашения предоставляется конструкцией аннотации с одним элементом (§9.7.3); необходимо соблюдать соглашение, чтобы воспользоваться конструкцией.
Пример 9.6.1-2. Объявления типов одноэлементных аннотаций
Соглашение проиллюстрировано в следующем объявлении типа аннотации:
/**
* Associates a copyright notice with the annotated API element.
*/
@interface Copyright {
String value();
}
Следующее объявление типа аннотации определяет одноэлементный тип аннотации, единственный элемент которого имеет тип массива:
/**
* Associates a list of endorsers with the annotated class.
*/
@interface Endorsers {
String[] value();
}
Следующее объявление типа аннотации показывает аннотацию Class, значение которой ограничено подстановочным знаком с границами:
interface Formatter {}
// Designates a formatter to pretty-print the annotated class
@interface PrettyPrinter {
Class<? extends Formatter> value();
}
Обратите внимание, что грамматика для объявлений типов аннотаций допускает другие объявления элементов, кроме объявлений методов. Например, можно объявить вложенный enum для использования вместе с типом аннотации:
@interface Quality {
enum Level { BAD, INDIFFERENT, GOOD }
Level value();
}
Пример 9.6.2-3. Объявление сложного типа аннотации
Вот пример сложного типа аннотации, то есть типа аннотации, который содержит один или несколько элементов, типы которых также являются типами аннотаций.
/**
* A person's name. This annotation type is not designed
* to be used directly to annotate program elements, but to
* define elements of other annotation types.
*/
@interface Name {
String first();
String last();
}
/**
* Indicates the author of the annotated program element.
*/
@interface Author {
Name value();
}
/**
* Indicates the reviewer of the annotated program element.
*/
@interface Reviewer {
Name value();
}
Элемент типа аннотации может иметь указанное значение по умолчанию. Это делается путем указания после его (пустого) списка параметров ключевого слова default и значения элемента по умолчанию.
Значения по умолчанию применяются динамически во время чтения аннотаций; значения по умолчанию не компилируются в аннотации. Таким образом, изменение значения по умолчанию влияет на аннотации даже в классах, которые были скомпилированы до внесения изменений (предполагая, что эти аннотации не имеют явного значения для элемента по умолчанию).
Для указания значения по умолчанию используется ElementValue (§9.7).
Это ошибка времени компиляции, если тип элемента не соизмерим (§9.7) со указанным значением по умолчанию.
Пример 9.6.2-1. Объявление типа аннотации со значениями по умолчанию
@interface RequestForEnhancementDefault {
int id(); // No default - must be specified in
// each annotation
String synopsis(); // No default - must be specified in
// each annotation
String engineer() default "[unassigned]";
String date() default "[unimplemented]";
}
Несколько типов аннотаций предопределены в библиотеках платформы Java SE. Некоторые из этих предопределенных типов аннотаций имеют особую семантику. Эта семантика указана в этом разделе. Этот раздел не предоставляет полную спецификацию для предопределенных аннотаций, содержащихся здесь; это роль соответствующих спецификаций API. Здесь указывается только та семантика, которая требует особого поведения со стороны компилятора Java или реализации виртуальной машины Java.
Тип аннотации java.lang.annotation.Target предназначен для использования в метааннотациях, которые указывают на вид программного элемента, к которому применим тип аннотации.
java.lang.annotation.Target имеет один элемент типа java.lang.annotation.ElementType[].
Ошибка времени компиляции возникает, если данная константа enum появляется более одного раза в аннотации, соответствующий тип которой java.lang.annotation.Target.
См. §7.4.1, §8.1.1, §8.3.1, §8.4.1, §8.4.3, §8.8.3, §8.9, §9.1.1, §9.3, §9.4, §9.6 и §14.4 для других эффектов аннотаций java.lang.annotation.Target.
Аннотации могут присутствовать только в исходном коде, или они могут присутствовать в двоичной форме класса или интерфейса. Аннотация, присутствующая в двоичной форме, может быть доступна или недоступна во время выполнения через библиотеки отражения платформы Java SE. Тип аннотации java.lang.annotation.Retention используется для выбора одного из этих вариантов.
Если аннотация a соответствует типу T, а T имеет (мета-)аннотацию m, которая соответствует java.lang.annotation.Retention, то:
-
Если
mимеет элемент, значение которого равноjava.lang.annotation.RetentionPolicy.SOURCE, то компилятор Java должен гарантировать, чтоaотсутствует в двоичном представлении класса или интерфейса, в котором появляетсяa. -
Если
mимеет элемент, значение которого равноjava.lang.annotation.RetentionPolicy.CLASSилиjava.lang.annotation.RetentionPolicy.RUNTIME, то компилятор Java должен гарантировать, чтоaпредставлен в двоичном представлении класса или интерфейса, в котором появляетсяa, если толькоmне аннотирует объявление локальной переменной.Аннотация объявления локальной переменной никогда не сохраняется в двоичном представлении.
Кроме того, если
mимеет элемент, значение которого равноjava.lang.annotation.RetentionPolicy.RUNTIME, библиотеки отражения платформы Java SE должны сделатьaдоступным во время выполнения.
Если T не имеет (мета-)аннотации m, которая соответствует java.lang.annotation.Retention, то компилятор Java должен рассматривать T так, как если бы он имел такую метааннотацию m с элементом, значение которого равно java.lang.annotation.RetentionPolicy.CLASS.
Тип аннотации java.lang.annotation.Inherited используется для указания того, что аннотации в классе C, соответствующие данному типу аннотации, наследуются подклассами C.
Программисты иногда перегружают объявление метода, когда они намереваются его переопределить, что приводит к тонким проблемам. Тип аннотации Override поддерживает раннее обнаружение таких проблем.
Классический пример касается метода equals. Программисты пишут следующее в классе Foo:
public boolean equals(Foo that) { ... }
когда они намереваются написать:
public boolean equals(Object that) { ... }
Это совершенно законно, но класс Foo наследует реализацию equals от Object, что может привести к некоторым очень тонким ошибкам.
Если объявление метода аннотировано аннотацией @Override, но метод не переопределяет и не реализует метод, объявленный в супертипе, или не является эквивалентным переопределению методу public из Object, возникает ошибка времени компиляции.
Это поведение отличается от Java SE 5.0, где @Override вызывала ошибку времени компиляции только в том случае, если она применялась к методу, который реализовывал метод из суперинтерфейса, который также отсутствовал в суперклассе.
Оговорка о переопределении метода public мотивирована использованием @Override в интерфейсе. Рассмотрим следующие объявления типов:
class Foo { @Override public int hashCode() {..} }
interface Bar { @Override int hashCode(); }
Использование @Override в объявлении класса законно в соответствии с первой оговоркой, потому что Foo.hashCode переопределяет Object.hashCode (§8.4.8).
Для объявления интерфейса учтите, что хотя интерфейс не имеет Object в качестве супертипа, интерфейс имеет public abstract члены, которые соответствуют public членам Object (§9.2). Если интерфейс решит объявить их явно (т.е. объявить члены, эквивалентные переопределению методам public из Object), то интерфейс считается их переопределяющим (§8.4.8), и использование @Override разрешено.
Однако рассмотрим интерфейс, который пытается использовать @Override в методе clone: (finalize также можно использовать в этом примере)
interface Quux { @Override Object clone(); }
Поскольку Object.clone не является public, в Quux неявно не объявлен член с именем clone. Поэтому явное объявление clone в Quux не считается «реализующим» какой-либо другой метод, и использование @Override является ошибочным. (Тот факт, что Quux.clone является public, не имеет значения.)
Напротив, объявление класса, объявляющее clone, просто переопределяет Object.clone, поэтому может использовать @Override:
class Beep { @Override protected Object clone() {..} }
Компиляторы Java все чаще способны выдавать полезные предупреждения типа «lint». Чтобы поощрять использование таких предупреждений, должен быть какой-то способ отключить предупреждение в части программы, когда программист знает, что предупреждение неуместно.
Тип аннотации SuppressWarnings поддерживает контроль программиста над предупреждениями, в противном случае выдаваемыми компилятором Java. Он содержит один элемент, представляющий собой массив String.
Если объявление программы аннотировано аннотацией @SuppressWarnings(value = {S1, ..., Sk}), то компилятор Java не должен сообщать о каких-либо предупреждениях, идентифицированных одним из S1 ... Sk, если это предупреждение было бы сгенерировано в результате аннотированного объявления или любой его части.
Непроверенные предупреждения идентифицируются строкой "unchecked".
Поставщики компиляторов должны документировать имена предупреждений, которые они поддерживают совместно с этим типом аннотации. Поставщикам рекомендуется сотрудничать, чтобы обеспечить работу одних и тех же имен в нескольких компиляторах.
Элемент программы, аннотированный @Deprecated, — это элемент, использование которого программистам не рекомендуется, как правило, потому, что он опасен или потому, что существует лучшая альтернатива.
Компилятор Java должен выдавать предупреждение об устаревании, когда тип, метод, поле или конструктор, объявление которого аннотировано аннотацией @Deprecated, используется (т. е. переопределен, вызван или упоминается по имени), если только:
-
Использование находится внутри сущности, которая сама аннотирована аннотацией
@Deprecated; или -
Использование находится внутри сущности, которая аннотирована для подавления предупреждения с помощью аннотации
@SuppressWarnings("deprecation"); или -
Использование и объявление находятся в одном и том же внешнем классе.
Использование аннотации @Deprecated в объявлении локальной переменной или в объявлении параметра не имеет никакого эффекта.
Параметр переменной арности с нереализуемым типом элемента ( §4.7 ) может вызвать загрязнение кучи ( §4.12.2 ) и привести к неконтролируемым предупреждениям времени компиляции ( §5.1.9 ). Такие предупреждения неинформативны, если тело метода переменной арности хорошо себя ведет по отношению к параметру переменной арности.
Тип аннотации SafeVarargs, при использовании для аннотирования объявления метода или конструктора, делает утверждение программиста, которое не позволяет компилятору Java сообщать о неконтролируемых предупреждениях для объявления или вызова метода или конструктора переменной арности, когда компилятор в противном случае сделал бы это из-за параметра переменной арности, имеющего нереализуемый тип элемента.
Аннотация SafeVarargs имеет нелокальные эффекты, потому что она подавляет неконтролируемые предупреждения в выражениях вызова метода в дополнение к неконтролируемому предупреждению, относящемуся к объявлению самого метода переменной арности ( §8.4.1 ). Напротив, аннотация @SuppressWarnings("unchecked") имеет локальные эффекты, потому что она только подавляет неконтролируемые предупреждения, относящиеся к объявлению метода.
Канонической целью для аннотации SafeVarargs является метод, такой как java.util.Collections.addAll, объявление которого начинается с:
public static <T> boolean addAll(Collection<? super T> c, T... elements)
Параметр переменной арности имеет объявленный тип T[], который нереализуем. Однако метод в основном просто читает из входного массива и добавляет элементы в коллекцию, обе из которых являются безопасными операциями по отношению к массиву. Поэтому любые неконтролируемые предупреждения времени компиляции в выражениях вызова метода для java.util.Collections.addAll, возможно, являются ложными и неинформативными. Применение аннотации SafeVarargs к объявлению метода предотвращает генерацию этих неконтролируемых предупреждений в выражениях вызова метода.
Это ошибка времени компиляции, если объявление метода или конструктора с фиксированной арностью аннотировано аннотацией SafeVarargs.
Это ошибка времени компиляции, если объявление метода переменной арности, которое не является ни static, ни final, аннотировано аннотацией SafeVarargs.
Поскольку аннотация SafeVarargs применима только к методам static, методам экземпляра final и конструкторам, аннотацию нельзя использовать, когда происходит переопределение метода. Наследование аннотаций работает только для классов (не для методов, интерфейсов или конструкторов), поэтому аннотацию в стиле SafeVarargs нельзя передать через методы экземпляра в классах или через интерфейсы.
Аннотация (annotation) — это модификатор, состоящий из имени типа аннотации (§9.6) и нуля или более пар «элемент-значение», каждая из которых связывает значение с другим элементом типа аннотации.
Цель аннотации — просто связать информацию с аннотированным элементом программы.
Аннотации должны содержать пару «элемент-значение» для каждого элемента соответствующего типа аннотации, за исключением тех элементов, которые имеют значения по умолчанию, иначе возникает ошибка времени компиляции.
Аннотации могут, но не обязаны, содержать пары «элемент-значение» для элементов со значениями по умолчанию.
Аннотации можно использовать в качестве модификаторов в любом объявлении, будь то пакет (§7.4.1), класс (§8.1.1) (включая перечисления (§8.9)), интерфейс (§9.1.1) (включая типы аннотаций (§9.6)), поле (§8.3.1, §9.3), метод (§8.4.3, §9.4), формальный параметр (§8.4.1), конструктор (§8.8.3) или локальная переменная (§14.4.1).
Аннотации также можно использовать в перечислениях констант. Такие аннотации располагаются непосредственно перед аннотируемой константой перечисления.
Во время компиляции возникает ошибка, если объявление аннотировано более чем одной аннотацией для данного типа аннотации.
Аннотации обычно располагаются перед всеми другими модификаторами, но это не является требованием; они могут свободно сочетаться с другими модификаторами.
Аннотации:
Annotation
Аннотации Annotation
Annotation:
NormalAnnotation
MarkerAnnotation
SingleElementAnnotation
Существуют три вида аннотаций. Первая (нормальная аннотация) является полностью общей. Остальные (маркерная аннотация и одноэлементная аннотация) — просто сокращения.
Нормальная аннотация используется для аннотирования элемента программы.
NormalAnnotation:
@ TypeName ( ElementValuePairsopt )
ElementValuePairs:
ElementValuePair
ElementValuePairs , ElementValuePair
ElementValuePair:
Identifier = ElementValue
ElementValue:
ConditionalExpression
Annotation
ElementValueArrayInitializer
ElementValueArrayInitializer:
{ ElementValuesopt ,opt }
ElementValues:
ElementValue
ElementValues , ElementValue
The TypeName names the annotation type corresponding to the annotation.
Обратите внимание, что символ at-sign (@) является токеном сам по себе. Технически можно поместить пробел между ним и TypeName, но это не рекомендуется в качестве стиля.
Это ошибка времени компиляции, если TypeName не называет тип аннотации, который доступен (§6.6) в точке, где используется аннотация.
Идентификатор (Identifier) в ElementValuePair должен быть простым именем одного из элементов (т. е. методов) типа аннотации, идентифицируемого TypeName; в противном случае возникает ошибка времени компиляции.
Тип возвращаемого значения этого метода определяет тип элемента пары «элемент-значение».
An ElementValueArrayInitializer is similar to a normal array initializer (§10.6), except that annotations are permitted in place of expressions.
Тип элемента T является commensurate со значением элемента V, если и только если выполняется одно из следующих условий:
-
T — это тип массива E
[]и либо:-
V— это ElementValueArrayInitializer, и каждый ElementValue (аналогично VariableInitializer в инициализаторе массива) вVсоизмерим с E; или -
V— это ElementValue, соизмеримое с E.
-
-
Тип
Vсовместим с присваиванием (§5.2) с T, и, кроме того:
Обратите внимание, что null не является допустимым значением элемента для любого типа элемента.
Во время компиляции возникает ошибка, если тип элемента не соизмерим с ElementValue.
Если тип элемента не является типом аннотации или типом массива, ElementValue должно быть ConditionalExpression (§15.25).
A ConditionalExpression is simply an expression without assignments, and not necessarily an expression involving the conditional operator (? :). ConditionalExpression is preferred over Expression in ElementValue because an element value has a simple structure (constant expression or class literal or enum constant) that may easily be represented in binary form.
Если тип элемента является типом массива, а соответствующий ElementValue не является ElementValueArrayInitializer, то значение массива, единственный элемент которого представляет значение, представленное ElementValue, связано с элементом. В противном случае, если соответствующий ElementValue является ElementValueArrayInitializer, то значение массива, представленное ElementValueArrayInitializer, связано с элементом.
Другими словами, допустимо опускать фигурные скобки, когда одноэлементный массив должен быть связан с элементом типа аннотации, принимающим массив в качестве значения.
Обратите внимание, что тип элемента массива не может быть типом массива. То есть вложенные типы массивов не допускаются в качестве типов элементов. (Хотя синтаксис аннотации это допустил бы, синтаксис объявления типа аннотации — нет.)
An ElementValue is always FP-strict (§15.4).
Аннотация в объявлении типа аннотации известна как meta-annotation.
Тип аннотации может использоваться для аннотирования собственного объявления. В более общем плане разрешены цикличности в транзитивном замыкании отношения «аннотирует».
Например, допустимо аннотировать объявление типа аннотации другим типом аннотации и аннотировать объявление последнего типа первым типом. (Предопределенные типы мета-аннотаций содержат несколько таких цикличностей.)
Пример 9.7.1-1. Нормальные аннотации
Вот пример нормальной аннотации.
@RequestForEnhancement(
id = 2868724,
synopsis = "Provide time-travel functionality",
engineer = "Mr. Peabody",
date = "4/1/2004"
)
public static void travelThroughTime(Date destination) { ... }
Вот пример нормальной аннотации, которая использует значения по умолчанию.
@RequestForEnhancement(
id = 4561414,
synopsis = "Balance the federal budget"
)
public static void balanceFederalBudget() {
throw new UnsupportedOperationException("Not implemented");
}
Обратите внимание, что типы аннотаций в примерах в этом разделе являются типами аннотаций, определенными в примерах в §9.6. Обратите также внимание, что элементы в приведенной выше аннотации находятся в том же порядке, что и в соответствующем объявлении типа аннотации. Это не обязательно, но, если особые обстоятельства не диктуют иное, это разумное соглашение.
Вторая форма аннотации, маркерная аннотация, представляет собой сокращение, предназначенное для использования с типами маркерных аннотаций.
MarkerAnnotation:
@ Identifier
Это сокращение для обычной аннотации:
@Identifier()
Разрешено использовать форму маркерной аннотации для типов аннотаций с элементами, если все элементы имеют значения по умолчанию.
Пример 9.7.2-1. Маркерные аннотации
Вот пример использования типа маркерной аннотации Preliminary из §9.6.1:
@Preliminary public class TimeTravel { ... }
Третья форма аннотации, одноэлементная аннотация, представляет собой сокращение, предназначенное для использования с одноэлементными типами аннотаций.
SingleElementAnnotation:
@ Identifier ( ElementValue )
Это сокращение для обычной аннотации:
@Identifier(value = ElementValue)
Разрешено использовать одноэлементные аннотации для типов аннотаций с несколькими элементами, если один элемент называется value, а все остальные элементы имеют значения по умолчанию.
Пример 9.7.3-1. Одноэлементные аннотации
Вот пример одноэлементной аннотации.
@Copyright("2002 Yoyodyne Propulsion Systems, Inc.")
public class OscillationOverthruster { ... }
Вот пример одноэлементной аннотации со значением массива.
@Endorsers({"Children", "Unscrupulous dentists"})
public class Lollipop { ... }
Вот пример одноэлементной аннотации со значением массива, состоящим из одного элемента. Обратите внимание, что фигурные скобки опущены.
@Endorsers("Epicurus")
public class Pleasure { ... }
Вот пример одноэлементной аннотации, содержащей обычную аннотацию.
@Author(@Name(first = "Joe", last = "Hacker"))
public class BitTwiddle { ... }
Вот пример одноэлементной аннотации с элементом Class, значение которого ограничено использованием ограниченного подстановочного знака.
class GorgeousFormatter implements Formatter { ... }
@PrettyPrinter(GorgeousFormatter.class)
public class Petunia { ... }
// Illegal; String is not a subtype of Formatter
@PrettyPrinter(String.class)
public class Begonia { ... }
Вот пример одноэлементной аннотации, использующей тип enum, определенный внутри типа аннотации.
@Quality(Quality.Level.GOOD)
public class Karma { ... }
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.