Глава 9. Интерфейсы
Содержание
- 9.1. Объявления интерфейсов
- 9.2. Члены интерфейса
- 9.3. Объявления полей (констант)
- 9.4. Объявления методов
- 9.5. Объявления вложенных классов и интерфейсов
- 9.6. Аннотационные интерфейсы
- 9.7. Аннотации
- 9.8. Функциональные интерфейсы
- 9.9. Типы функций
Объявление интерфейса определяет новый интерфейс, который может быть реализован одним или несколькими классами. Программы могут использовать интерфейсы для предоставления общего супертипа для несвязанных классов и для того, чтобы избежать необходимости в общем abstract суперклассе для родственных классов.
Интерфейсы не содержат переменных экземпляров и обычно объявляют один или несколько abstract методов; несвязанные классы могут реализовать интерфейс, предоставив реализации его abstract методов. Интерфейсы нельзя непосредственно создавать.
Интерфейс верхнего уровня (§7.6) — это интерфейс, объявленный непосредственно в единице компиляции.
Вложенный интерфейс — это любой интерфейс, объявление которого находится внутри тела другого класса или интерфейса. Вложенный интерфейс может быть интерфейсом-членом (§8.5, §9.5) или локальным интерфейсом (§14.3).
Аннотационный интерфейс (§9.6) — это интерфейс, объявленный с отличной синтаксической конструкцией, предназначенный для реализации рефлексивных представлений аннотаций (§9.7).
В этой главе рассматриваются общие семантики всех интерфейсов. Подробности, специфичные для определённых видов интерфейсов, рассматриваются в разделах, посвящённых этим конструкциям.
Интерфейс может быть объявлен прямым расширением одного или нескольких других интерфейсов, что означает, что он наследует все классы и интерфейсы-члены, методы экземпляров и static поля расширяемых интерфейсов, за исключением членов, которые он может переопределять или скрывать.
Класс может быть объявлен прямо реализующим один или несколько интерфейсов (§8.1.5), что означает, что любой экземпляр класса реализует все abstract методы, указанные интерфейсом или интерфейсами. Класс обязательно реализует все интерфейсы, которые реализуют его прямые суперклассы и прямые супер-интерфейсы. Это (множественное) наследование интерфейсов позволяет объектам поддерживать (множественные) общие действия без общего суперкласса.
В отличие от класса, интерфейс нельзя объявить final. Однако, интерфейс может быть объявлен sealed (§9.1.1.4) для ограничения его подклассов и под-интерфейсов.
Переменная, тип которой объявлен как тип интерфейса, может иметь в качестве значения ссылку на любой экземпляр класса, который реализует указанный интерфейс. Недостаточно, чтобы класс случайно реализовывал все abstract методы интерфейса; класс или один из его суперклассов должен быть фактически объявлен как реализующий интерфейс, в противном случае класс не считается реализующим интерфейс.
Объявление интерфейса определяет интерфейс.
Существуют два вида объявлений интерфейсов: обычные объявления интерфейсов и объявления интерфейсов-аннотаций (§9.6).
ИдентификаторТипа в объявлении интерфейса указывает имя интерфейса.
Если интерфейс имеет то же простое имя, что и любой из его окружающих классов или интерфейсов, это ошибка времени компиляции.
Область действия и перекрытие объявления интерфейса указаны в §6.3 и §6.4.1.
Объявление интерфейса может содержать модификаторы интерфейса.
Правила, касающиеся модификаторов аннотаций для объявления интерфейса, указаны в §9.7.4 и §9.7.5.
Модификатор доступа public (§6.6) относится только к интерфейсам верхнего уровня (§7.6) и вложенным интерфейсам (§8.5, §9.5), а не к локальным интерфейсам (§14.3).
Модификаторы доступа protected и private относятся только к вложенным интерфейсам.
Модификатор static относится только к вложенным и локальным интерфейсам.
Ошибка времени компиляции, если одно и то же ключевое слово появляется более одного раза как модификатор для объявления интерфейса, или если объявление интерфейса имеет более одного из модификаторов доступа public, protected и private.
Ошибка времени компиляции, если объявление интерфейса имеет более одного из модификаторов sealed и non-sealed.
Если в объявлении интерфейса присутствуют два или более (различных) модификаторов интерфейса, то обычно, хотя и не обязательно, они располагаются в порядке, согласованном с представленным выше в выражении для МодификатораИнтерфейса.
Каждый интерфейс неявным образом abstract.
Этот модификатор устарел и не должен использоваться в новом коде.
Модификатор strictfp в объявлении интерфейса устарел и не должен использоваться в новом коде. Его присутствие или отсутствие не влияет на время компиляции или выполнения.
Вложенный интерфейс неявным образом static. То есть каждый вложенный и локальный интерфейс static. Допускается для объявления вложенного интерфейса избыточно указывать модификатор static (§9.5), но недопустимо для объявления локального интерфейса (§14.3).
Поскольку вложенный интерфейс static, у него нет непосредственного окружающего экземпляра (§8.1.3). Ссылки из вложенного интерфейса на параметры типа, переменные экземпляра, локальные переменные, формальные параметры, параметры исключений или методы экземпляра в лексически окружающих класс, интерфейс или метод объявления запрещены (§6.5.5.1, §6.5.6.1, §15.12.3).
Интерфейс может быть объявлен sealed, если все его непосредственные подклассы и непосредственные подинтерфейсы известны при объявлении интерфейса (§9.1.4), и другие непосредственные подклассы или непосредственные подинтерфейсы не требуются.
Полезно вспомнить, что класс считается непосредственным подклассом своих непосредственных суперинтерфейсов (§8.1.5).
Интерфейс является свободно расширяемым, если ни один из его непосредственных суперинтерфейсов не является sealed (§9.1.3), и он сам не является sealed.
Интерфейс, имеющий sealed непосредственный суперинтерфейс, является свободно расширяемым, только если он объявлен non-sealed.
Ошибка времени компиляции, если интерфейс имеет sealed непосредственный суперинтерфейс и не объявлен sealed или non-sealed.
Ошибка времени компиляции, если интерфейс объявлен non-sealed, но не имеет sealed непосредственного суперинтерфейса.
Интерфейс является обобщенным, если объявление интерфейса объявляет одну или несколько переменных типа (§4.4).
Эти переменные типа известны как параметры типа интерфейса. Раздел параметров типа следует за именем интерфейса и ограничен угловыми скобками.
Ниже для удобства показаны следующие продуктивные правила из §8.1.2 и §4.4:
Правила, касающиеся модификаторов аннотаций для объявления параметра типа, указаны в §9.7.4 и §9.7.5.
В разделе параметров типа интерфейса переменная типа T непосредственно зависит от переменной типа S, если S является границей T, а T зависит от S, если либо T непосредственно зависит от S, либо T непосредственно зависит от переменной типа U, которая зависит от S (используя это определение рекурсивно). Ошибка компиляции возникает, если переменная типа в разделе параметров типа интерфейса зависит от себя.
Область действия и перекрытие параметра типа интерфейса указаны в §6.3 и §6.4.1.
Ссылки на параметр типа интерфейса из статического контекста или вложенного класса или интерфейса ограничены, как указано в §6.5.5.1.
Объявление обобщенного интерфейса определяет набор типизированных типов (§4.5), по одному для каждой возможной параметризации раздела параметра типа аргументами типа. Все эти типизированные типы совместно используют один и тот же интерфейс во время выполнения.
Если предоставлен раздел extends, то объявляемый интерфейс расширяет каждый из указанных типов интерфейса и, следовательно, наследует классы-члены, интерфейсы-члены, методы экземпляра и static поля каждого из этих типов интерфейса.
Указанные типы интерфейса являются непосредственными типами суперинтерфейса объявляемого интерфейса.
Любой класс, который implements объявленный интерфейс, также считается реализующим все интерфейсы, которые этот интерфейс extends.
extends Список типов интерфейса Ниже для удобства показано следующее правило из §8.1.5:
Каждый Тип интерфейса в разделе extends объявления интерфейса должен называть доступный интерфейс (§6.6), в противном случае произойдет ошибка компиляции.
Ошибка компиляции возникает, если любой Тип интерфейса называет интерфейс, который sealed (§9.1.1.4), и объявляемый интерфейс не является разрешенным непосредственным подинтерфейсом названного интерфейса (§9.1.4).
Если Тип интерфейса имеет аргументы типа, он должен обозначать правильно сформированный типизированный тип (§4.5), и ни один из аргументов типа не может быть аргументами типа подстановочных значений, в противном случае произойдет ошибка компиляции.
Один интерфейс является непосредственным суперинтерфейсом другого интерфейса, если первый интерфейс указан в одном из непосредственных типов суперинтерфейса второго интерфейса.
Отношение суперинтерфейса является транзитивным замыканием отношения непосредственного суперинтерфейса. Интерфейс I является суперинтерфейсом интерфейса K, если выполняется одно из следующих условий:
-
I является непосредственным суперинтерфейсом K.
-
Если J является непосредственным суперинтерфейсом K, I является суперинтерфейсом J, применяя это определение рекурсивно.
Интерфейс называется непосредственным подинтерфейсом своего непосредственного суперинтерфейса и подинтерфейсом каждого из своих суперинтерфейсов.
Хотя каждый класс является расширением класса Object, нет единого интерфейса, которым являются расширения всех интерфейсов.
Интерфейс I непосредственно зависит от класса или интерфейса A, если A упоминается в разделе extends I либо как суперинтерфейс, либо как квалификатор в полном квалифицированном имени суперинтерфейса.
Интерфейс I зависит от класса или интерфейса A, если выполняется любое из следующих условий:
-
I непосредственно зависит от A.
-
I непосредственно зависит от класса C, который зависит от A (§8.1.5).
-
I непосредственно зависит от интерфейса J, который зависит от A, применяя это определение рекурсивно.
Ошибка компиляции возникает, если интерфейс зависит от себя.
Если при загрузке интерфейсов будут обнаружены циклически объявленные интерфейсы, будет сгенерирована ошибка ClassCircularityError (§12.2.1).
Необязательная permits фраза в объявлении обычного интерфейса указывает все классы и интерфейсы, предназначенные в качестве прямых подклассов и прямых подинтерфейсов объявляемого интерфейса (§9.1.1.4).
Если в объявлении интерфейса есть фраза permits, но отсутствует модификатор sealed, то это ошибка компиляции.
Каждый ИмяТипа должен указывать на доступный класс или интерфейс (§6.6), иначе произойдёт ошибка компиляции.
Если один и тот же класс или интерфейс указан более одного раза во фразе permits, то это ошибка компиляции. Это верно даже если класс или интерфейс назван по-разному.
Каноническое имя класса или интерфейса не обязательно использовать во фразе permits, но фраза permits может указывать на класс или интерфейс только один раз. Например, следующая программа не скомпилируется:
package p;
sealed interface I permits C, D, p.C {} // error
non-sealed class C implements I {}
non-sealed class D implements I {}
Если интерфейс sealed I ассоциирован с именованным модулем (§7.3), то каждый класс или интерфейс, указанный во фразе permits объявления I, должен быть ассоциирован с тем же модулем, что и I, иначе произойдёт ошибка компиляции.
Если интерфейс sealed I ассоциирован с безымянным модулем (§7.7.5), то каждый класс или интерфейс, указанный во фразе permits объявления I, должен принадлежать к тому же пакету, что и I, иначе произойдёт ошибка компиляции.
Интерфейс sealed и его прямые подклассы и прямые подинтерфейсы должны ссылаться друг на друга циклическим способом, соответственно, во фразах permits, implements и extends. Поэтому в модульной кодовой базе они должны быть размещены в одном модуле, так как классы и интерфейсы в разных модулях не могут ссылаться друг на друга циклическим образом. Совместное размещение желательно в любом случае, потому что иерархия интерфейса sealed всегда должна быть объявлена в одном домене поддержки, за которым отвечает один и тот же разработчик или группа разработчиков. Именованный модуль обычно представляет собой домен поддержки в модульной кодовой базе.
Если объявление интерфейса sealed I содержит фразу permits, то разрешённые прямые подклассы и подинтерфейсы интерфейса I представляют собой классы и интерфейсы, указанные во фразе permits.
Каждый разрешённый прямой подкласс и подинтерфейс, указанный во фразе permits, должен быть прямым подклассом интерфейса I (§8.1.5) или прямым подинтерфейсом I (§9.1.3), иначе произойдёт ошибка компиляции.
Если объявление интерфейса sealed I не содержит фразу permits, то разрешённые прямые подклассы и подинтерфейсы интерфейса I — это те классы и интерфейсы, которые объявлены в той же единице компиляции, что и I (§7.3), имеют каноническое имя (§6.7) и для которых прямые суперинтерфейсы включают интерфейс I.
То есть разрешённые прямые подклассы и подинтерфейсы вычисляются как классы и интерфейсы в той же единице компиляции, которые указывают интерфейс I в качестве непосредственного суперинтерфейса. Требование канонического имени означает, что локальные классы, локальные интерфейсы или анонимные классы не будут рассмотрены.
Если объявление интерфейса sealed I не содержит фразу permits и у I нет разрешённых прямых подклассов или подинтерфейсов, то это ошибка компиляции.
Членами интерфейса являются:
-
Члены, объявленные в теле объявления интерфейса (§9.1.5).
-
Члены, унаследованные от любых прямых суперинтерфейсов (§9.1.3).
-
Если у интерфейса нет прямых суперинтерфейсов, то интерфейс неявно объявляет метод
publicabstractдля каждогоpublicэкземпляра методаmс сигнатурой s, возвращаемым типом r иthrowsфразрой t, объявленного вObject(§4.3.2), за исключением случаев, когда интерфейс явно объявляет метод с той же сигнатурой, тем же возвращаемым типом и совместимойthrowsфразой.Ошибка компиляции, если интерфейс явно объявляет такой метод
mв случае, когдаmобъявлен какfinalвObject.Ошибка компиляции, если интерфейс явно объявляет метод с сигнатурой, которая эквивалентна переопределению (§8.4.2) для
publicметодаObject, но с другим возвращаемым типом или несовместимойthrowsфразой, или не являетсяabstract.
Интерфейс наследует от интерфейсов, которые он расширяет, все члены этих интерфейсов, за исключением (i) полей, классов и интерфейсов, которые он скрывает, (ii) abstract методов и методов по умолчанию, которые он переопределяет (§9.4.1), (iii) private методов и (iv) static методов.
Поля, методы, вложенные классы и вложенные интерфейсы интерфейса могут иметь одинаковые имена, поскольку они используются в разных контекстах и различаются различными процедурами поиска (§6.5). Однако это не рекомендуется с точки зрения стиля.
См. §8.3 для ТипБезАннотаций. Следующие правила из §4.3 и §8.3 показаны здесь для удобства:
Правила, касающиеся модификаторов аннотаций для объявления поля интерфейса, описаны в §9.7.4 и §9.7.5.
Каждое объявление поля в теле объявления интерфейса неявно public, static и final. Разрешено избыточно указывать любой или все из этих модификаторов для таких полей.
Ошибка компиляции, если одно и то же ключевое слово используется более одного раза как модификатор в объявлении поля.
Если в объявлении поля присутствуют два или более (различных) модификатора поля, то по условному соглашению, хотя и необязательно, они должны быть указаны в том порядке, который показан выше в правиле для МодификаторКонстанты.
Объявленный тип поля обозначается ТипБезАннотаций, если в ТипБезАннотаций и ИдентификаторДескриптораПеременной не появляются пары скобок, и определяется в §10.2 в противном случае.
Область действия и перекрытие объявления поля интерфейса задаются в §6.3 и §6.4.1.
Поскольку поле интерфейса static, его объявление вводит статический контекст (§8.1.3), который ограничивает использование конструкций, ссылающихся на текущий объект. Заметно, что ключевые слова this и super запрещены в статическом контексте (§15.8.3, §15.11.2), как и неквалифицированные ссылки на переменные экземпляров, методы экземпляров и параметры типа лексически окружающих объявлений (§6.5.5.1, §6.5.6.1, §15.12.3).
Ошибка компиляции, если тело объявления интерфейса объявляет два поля с одинаковым именем.
Если интерфейс объявляет поле с определенным именем, то объявление этого поля считается скрывающим все доступные объявления полей с тем же именем в суперинтерфейсах интерфейса.
Интерфейс может унаследовать более одного поля с одинаковым именем. Такая ситуация сама по себе не вызывает ошибки компиляции. Однако любая попытка внутри тела объявления интерфейса сослаться на любое такое поле по его простому имени приведет к ошибке компиляции, поскольку ссылка является неоднозначной.
Может быть несколько путей, по которым одно и то же объявление поля унаследовано от интерфейса. В такой ситуации поле считается унаследованным только один раз, и к нему можно обратиться по его простому имени без неоднозначности.
Пример 9.3-1. Неоднозначные унаследованные поля
Если два поля с одинаковым именем унаследованы интерфейсом, потому что, например, два его прямых суперинтерфейса объявляют поля с этим именем, то получается одно неоднозначное поле. Любое использование этого неоднозначного поля приведет к ошибке компиляции. В программе:
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 полю YELLOW вместо значения 8, ссылка на поле YELLOW внутри интерфейса LotsOfColors по-прежнему будет считаться неоднозначной.
Пример 9.3-2. Поля, унаследованные несколько раз
Если одно поле унаследовано несколько раз от одного и того же интерфейса, потому что, например, этот интерфейс и один из его прямых суперинтерфейсов расширяют интерфейс, который объявляет это поле, то получается только одно поле. Эта ситуация сама по себе не вызывает ошибку компиляции.
В предыдущем примере поля RED, GREEN и BLUE унаследованы интерфейсом LotsOfColors более чем одним способом, через интерфейс RainbowColors и также через интерфейс PrintColors, но ссылка на поле RED в интерфейсе LotsOfColors не считается неоднозначной, поскольку задействовано только одно фактическое объявление поля RED.
Каждый дескриптор в объявлении поля интерфейса должен иметь инициализатор переменной, в противном случае возникает ошибка компиляции.
Инициализатор необязательно должен быть константным выражением (§15.29).
Ошибка компиляции, если инициализатор поля интерфейса использует простое имя того же поля или другого поля, объявление которого расположено правее инициализатора (§3.5) в том же интерфейсе.
Инициализатор поля интерфейса не может ссылаться на текущий объект, используя ключевое слово this или ключевое слово super, как указано в §15.8.3, §15.11.2, и §15.12.3.
Во время выполнения инициализатор вычисляется и присваивание поля выполняется ровно один раз при инициализации интерфейса (§12.4.2).
Обратите внимание, что поля интерфейса, являющиеся константными переменными (§4.12.4), инициализируются перед другими полями интерфейса. Это также относится к static полям, которые являются константными переменными в классах (§8.3.2). Такие поля никогда не будут иметь своих значений по умолчанию (§4.12.5), даже в хитроумных программах.
Пример 9.3.1-1. Ссылка на поле вперёд
interface Test {
float f = j;
int j = 1;
int k = k + 1;
}
Эта программа вызывает две ошибки компиляции, поскольку j ссылается на f в инициализации j до объявления k, и поскольку инициализация k ссылается на само k.
Следующие правила из §8.4, §8.4.5 и §8.4.7 показаны здесь для удобства:
Правила, касающиеся модификаторов аннотаций для объявления метода интерфейса, указаны в §9.7.4 и §9.7.5.
Метод в теле объявления интерфейса может быть объявлен public или private (§6.6). Если модификатор доступа не указан, метод неявно public. Разрешается, но не рекомендуется по стилю, избыточно указывать модификатор public для объявления метода в объявлении интерфейса.
Метод по умолчанию — это метод экземпляра, объявленный в интерфейсе с модификатором default. Его тело всегда представлено блоком, который предоставляет реализацию по умолчанию для любого класса, реализующего интерфейс без переопределения метода. Методы по умолчанию отличаются от конкретных методов (§8.4.3.1), которые объявляются в классах, и от методов private интерфейса, которые ни наследуются, ни переопределяются.
Интерфейс может объявлять static методы, которые вызываются без ссылки на конкретный объект. static методы интерфейса отличаются от методов по умолчанию, abstract методов интерфейса и не-static private методов интерфейса, которые являются методами экземпляра.
Объявление static метода интерфейса вводит статический контекст (§8.1.3), который ограничивает использование конструкций, ссылающихся на текущий объект. Обратите внимание, что ключевые слова this и super запрещены в статическом контексте (§15.8.3, §15.11.2), как и неквалифицированные ссылки на переменные экземпляра, методы экземпляра и параметры типа лексически окружающих объявлений (§6.5.5.1, §6.5.6.1, §15.12.3).
Ссылки на метод экземпляра из статического контекста или вложенного класса или интерфейса ограничены (§15.12.3).
Модификатор strictfp в объявлении метода интерфейса устарел и не должен использоваться в новом коде. Его наличие или отсутствие не влияет на выполнение.
Метод интерфейса без модификатора private, default или static неявно abstract. Его тело представлено точкой с запятой, а не блоком. Разрешается, но не рекомендуется по стилю, избыточно указывать модификатор abstract для такого объявления метода.
Обратите внимание, что метод интерфейса не может быть объявлен с protected или пакетом доступа, или с модификаторами final, synchronized или native.
Ошибка компиляции, если одно и то же ключевое слово используется более одного раза в качестве модификатора для объявления метода интерфейса, или если объявление метода интерфейса имеет более одного из модификаторов доступа public и private (§6.6).
Ошибка компиляции, если объявление метода интерфейса имеет более одного из ключевых слов abstract, default или static.
Ошибка компиляции, если объявление метода интерфейса, содержащее ключевое слово private, также содержит ключевое слово abstract или default. Разрешается объявление метода интерфейса содержать как private, так и static.
Ошибка компиляции, если объявление метода интерфейса, содержащее ключевое слово abstract, также содержит ключевое слово strictfp.
Ошибка компиляции, если тело объявления интерфейса объявляет явным или неявным образом два метода с эквивалентными по перекрытию сигнатурами (§8.4.2). Однако интерфейс может унаследовать несколько abstract методов с такими сигнатурами (§9.4.1).
Метод, объявленный в интерфейсе, может быть обобщенным. Правила для параметров типа обобщенного метода в интерфейсе такие же, как для обобщенного метода в классе (§8.4.4).
Интерфейс I унаследует от своих непосредственных суперинтерфейсов все abstract и методы по умолчанию m, для которых выполняются все следующие условия:
-
mявляется членом непосредственного суперинтерфейса типа I, J. -
Ни один метод, объявленный в I, не имеет сигнатуры, являющейся подсигнатурой (§8.4.2) сигнатуры
mкак члена J. -
Не существует метода
m', который является членом непосредственного суперинтерфейса I, J' (mотличающегося отm', J отличается от J'), такого, чтоm'переопределяет объявление методаmиз интерфейса J' (§9.4.1.1).
Обратите внимание, что методы переопределяются на основе сигнатуры. Если, например, интерфейс объявляет два public метода с одинаковым именем (§9.4.2), и подинтерфейс переопределяет один из них, подинтерфейс все равно наследует другой метод.
Третье условие выше предотвращает подинтерфейс от повторного наследования метода, который уже был переопределен другим из его суперинтерфейсов. Например, в этой программе:
interface Top {
default String name() { return "unnamed"; }
}
interface Left extends Top {
default String name() { return getClass().getName(); }
}
interface Right extends Top {}
interface Bottom extends Left, Right {}
Right наследует name() от Top, но Bottom наследует name() от Left, а не от Right. Это происходит потому, что name() из Left переопределяет объявление name() в Top.
Интерфейс не наследует private или static методы от своих суперинтерфейсов.
Если интерфейс I объявляет private или static метод m, и сигнатура m является подсигнатурой public метода экземпляра m' в супертипе I, и m' в противном случае был бы доступен коду в I, то происходит ошибка во время компиляции.
По существу, метод static в интерфейсе не может скрыть метод экземпляра в суперинтерфейсе. Это аналогично правилу в §8.4.8.2, где метод static в классе не может скрыть метод экземпляра в суперклассе или суперинтерфейсе. Обратите внимание, что правило в §8.4.8.2 говорит о классе, который "объявляет или наследует static метод", в то время как правило выше говорит только об интерфейсе, который "объявляет static метод", поскольку интерфейс не может унаследовать static метод. Также обратите внимание, что правило в §8.4.8.2 позволяет скрывать методы экземпляров и static методы в суперклассах/суперинтерфейсах, в то время как правило выше рассматривает только public методы экземпляров в суперинтерфейсах.
В том же духе, private метод в интерфейсе не может переопределить метод экземпляра - будь то public или private - в суперинтерфейсе. Это аналогично правилам в §8.4.8.1 и §8.4.8.3, где метод private в классе не может переопределить никакой метод экземпляра в суперклассе или суперинтерфейсе, так как §8.4.8.1 требует, чтобы переопределяемый метод был не-private, а §8.4.8.3 требует, чтобы переопределяющий метод предоставлял не менее доступа, чем переопределяемый метод. В заключение, только public методы в интерфейсах могут быть переопределены, и только public методами в подинтерфейсах или в реализующих классах.
Метод экземпляра mI, объявленный в интерфейсе I или унаследованный от него, переопределяет из I другой метод экземпляра mJ, объявленный в интерфейсе J, если выполняются все следующие условия:
-
I является подинтерфейсом J.
-
I не наследует
mJ. -
Сигнатура
mIявляется подсигнатурой (§8.4.2) сигнатурыmJкак члена супертипа I, который называет J. -
mJявляетсяpublic.
Наличие или отсутствие модификатора strictfp совершенно не влияет на правила переопределения методов. Например, разрешено, чтобы метод, который не является strictfp, переопределял strictfp метод, и разрешено, чтобы strictfp метод переопределял метод, который не является strictfp.
К методу по умолчанию, который был переопределен, можно получить доступ, используя выражение вызова метода (§15.12), содержащее ключевое слово super, квалифицированное именем суперинтерфейса.
Отношения между типом возвращаемого значения метода интерфейса и типами возвращаемых значений любых переопределяемых методов интерфейса указаны в §8.4.8.3.
Отношения между throws частью метода интерфейса и throws частями любых переопределяемых методов интерфейса указаны в §8.4.8.3.
Отношения между сигнатурой метода интерфейса и сигнатурами любых переопределяемых методов интерфейса указаны в §8.4.8.3.
Отношения между доступом к методу интерфейса и доступом к любым переопределяемым методам интерфейса указаны в §8.4.8.3.
Если метод по умолчанию эквивалентен переопределению (§8.4.2) с методом, не являющимся private методом класса Object, это ошибка времени компиляции, так как любой класс, реализующий интерфейс, унаследует собственную реализацию метода.
Запрет на объявление одного из методов Object в качестве метода по умолчанию может показаться неожиданным. В конце концов, есть такие случаи, как java.util.List, в которых поведение toString и equals точно определены. Однако мотивация становится яснее, когда понимаются некоторые более общие решения по проектированию:
-
Во-первых, методы, унаследованные от суперкласса, могут переопределять методы, унаследованные от суперинтерфейсов (§8.4.8.1). Таким образом, каждый реализующий класс автоматически переопределит
toStringметод по умолчанию интерфейса. Это давнее поведение языка программирования Java. Это не то, что мы хотим изменить с помощью методов по умолчанию, поскольку это противоречит цели позволяющей интерфейсам эволюционировать незаметно, предоставляя поведение по умолчанию только тогда, когда класс уже не имеет его через иерархию классов. -
Во-вторых, интерфейсы не наследуют от
Object, а скорее неявно объявляют многие из тех же методов, что иObject(§9.2). Таким образом, нет общего предка дляtoString, объявленного вObject, иtoString, объявленного в интерфейсе. В лучшем случае, если оба были кандидатами на наследование классом, они вступили бы в конфликт. Обход этой проблемы потребовал бы неуклюжего смешения древовидных структур наследования классов и интерфейсов. -
Во-третьих, случаи использования объявления методов
Objectв интерфейсах обычно предполагают линейную иерархию интерфейсов; функция не обобщается хорошо на сценарии множественного наследования. -
В-четвертых, методы
Objectнастолько фундаментальны, что кажется опасным разрешать произвольному суперинтерфейсу молча добавлять метод по умолчанию, изменяющий их поведение.
Однако интерфейс свободен определять другой метод, который предоставляет поведение, полезное для классов, переопределяющих методы Object. Например, интерфейс java.util.List может объявить метод elementString, который генерирует строку, описанную контрактом метода toString; реализаторы метода toString в классах могли бы делегировать этому методу.
Интерфейс может унаследовать несколько методов с эквивалентными сигнатурами переопределения (§8.4.2).
Если интерфейс I наследует метод по умолчанию, чья сигнатура эквивалентна переопределению другому методу, унаследованному интерфейсом I, возникает ошибка времени компиляции. (Это происходит, независимо от того, является ли другой метод abstract или default.)
В противном случае все унаследованные методы являются abstract, и интерфейс считается унаследовавшим все методы.
Один из унаследованных методов должен быть подстановкой типа возвращаемого значения для каждого другого унаследованного метода, в противном случае возникает ошибка времени компиляции. (throws части в этом случае не вызывают ошибок.)
Возможно несколько путей, по которым одна и та же декларация метода унаследована от интерфейса. Этот факт не создаёт трудностей и никогда сам по себе не приводит к ошибке времени компиляции.
Естественно, когда два разных метода по умолчанию с совпадающими сигнатурами наследуются подинтерфейсом, возникает конфликт поведения. Мы активно обнаруживаем этот конфликт и сообщаем программисту об ошибке, а не ждём, пока проблема возникнет при компиляции конкретного класса. Ошибку можно избежать, объявив новый метод, который переопределяет и тем самым предотвращает наследование всех конфликтующих методов.
Аналогично, когда abstract метод и default метод с совпадающими сигнатурами наследуются подинтерфейсом, мы выдаём ошибку. В этом случае можно было бы отдать приоритет одному из них — возможно, мы бы предположили, что метод по умолчанию предоставляет разумную реализацию для abstract метода. Но это рискованно, поскольку, кроме случайного совпадения имени и сигнатуры, у нас нет оснований полагать, что поведение метода по умолчанию согласуется с контрактом abstract метода — метод по умолчанию мог вообще не существовать во время первоначального разработки подинтерфейса. В этой ситуации безопаснее попросить пользователя явно указать, что реализация по умолчанию является подходящей (путем объявления переопределения).
В отличие от этого, давнее поведение унаследованных конкретных методов в классах заключается в том, что они переопределяют abstract методы, объявленные в интерфейсах (см. §8.4.8). Тот же аргумент о потенциальном нарушении контракта применим и здесь, но в этом случае существует некоторая несоразмерность между классами и интерфейсами. Для сохранения независимости иерархий классов мы предпочитаем минимизировать конфликты класса и интерфейса, просто отдавая приоритет конкретным методам.
Если два метода интерфейса (будь то объявленные в одном интерфейсе, или унаследованные одним интерфейсом, или один объявленный, а другой унаследованный) имеют одинаковое имя, но разные сигнатуры, которые не эквивалентны переопределению (§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, должен предоставить реализации всех трёх сигнатур метода.
Метод по умолчанию имеет тело блока. Этот блок кода обеспечивает реализацию метода в случае, если класс реализует интерфейс, но не предоставляет собственную реализацию метода.
Метод интерфейса private или static также имеет тело блока, которое обеспечивает реализацию метода.
Ошибка времени компиляции, если объявление метода интерфейса abstract (явное или неявное) и имеет блок для своего тела.
Ошибка времени компиляции, если объявление метода интерфейса default, private или static, и имеет точку с запятой для своего тела.
Правила для return операторов в теле метода указаны в §14.17.
Если метод объявлен с типом возврата (§8.4.5), возникает ошибка времени компиляции, если тело метода может завершиться нормально (§14.1).
Тело интерфейса (§9.1.5) может содержать объявления вложенных классов и вложенных интерфейсов (§8.5).
Каждое объявление вложенного класса или интерфейса в теле объявления интерфейса неявно public и static (§9.1.1.3). Разрешается избыточно указывать один или оба этих модификатора.
Если объявление вложенного класса или интерфейса в интерфейсе имеет модификатор protected или private, это ошибка компиляции.
Правила модификаторов объявления вложенного класса в теле объявления интерфейса указаны в §8.1.1.
Правила модификаторов объявления вложенного интерфейса в теле объявления интерфейса указаны в §9.1.1.
Если интерфейс объявляет вложенный класс или интерфейс с определенным именем, то объявление этого вложенного класса или интерфейса считается скрывающим все доступные объявления вложенных классов и интерфейсов с тем же именем в суперинтерфейсах интерфейса.
Интерфейс наследует от своих непосредственных суперинтерфейсов все вложенные классы и интерфейсы непосредственных суперинтерфейсов, которые не скрыты объявлением в интерфейсе.
Возможна ситуация, когда интерфейс наследует более одного вложенного класса или интерфейса с одинаковым именем. Такая ситуация сама по себе не приводит к ошибке компиляции. Однако любая попытка сослаться в теле интерфейса на такой вложенный класс или интерфейс по его простому имени приведет к ошибке компиляции, потому что ссылка является неоднозначной.
Может быть несколько путей наследования одного и того же объявления вложенного класса или интерфейса из интерфейса. В такой ситуации вложенный класс или интерфейс считается унаследованным только один раз, и на него можно сослаться по его простому имени без неоднозначности.
Объявление интерфейса аннотации определяет интерфейс аннотации, специализированный вид интерфейса. Чтобы отличить объявление интерфейса аннотации от обычного объявления интерфейса, ключевое слово interface предшествует знаку "@" (@).
Обратите внимание, что знак "@" (@) и ключевое слово interface — это отдельные лексемы. Их можно разделить пробелами, но это не рекомендуется с точки зрения стиля.
Если в этом разделе и его подразделах не указано иное, все правила, применимые к обычным объявлениям интерфейсов (§9.1), применяются к объявлениям интерфейсов аннотаций.
Например, для объявления интерфейсов аннотаций действуют те же правила области видимости, что и для обычных объявления интерфейсов.
Если объявление интерфейса аннотации имеет модификатор sealed или non-sealed (§9.1.1.4), это ошибка компиляции.
Объявление интерфейса аннотации может определять верхнеуровневый интерфейс или вложенный интерфейс, но не локальный интерфейс (§14.3).
Синтаксически объявление интерфейса аннотации не может находиться внутри блока в силу производства LocalClassOrInterfaceDeclaration в §14.3.
Если объявление интерфейса аннотации появляется непосредственно или косвенно в теле локального класса, локального интерфейса или анонимного класса, это ошибка компиляции (§14.3, §15.9.5).
Это правило, вместе со синтаксическим ограничением на объявления интерфейсов аннотаций, упомянутым выше, гарантирует, что интерфейс аннотации всегда имеет каноническое имя (§6.7). Наличие такого имени важно, потому что цель интерфейса аннотации — использоваться аннотациями в других модулях компиляции. Поскольку локальный класс или интерфейс не имеет канонического имени, объявленный где-либо в его синтаксическом теле (если это было бы разрешено) интерфейс аннотации тоже не имел бы канонического имени.
Следующий код демонстрирует действие этого правила и связанного синтаксического ограничения:
class C {
@interface A1 {} /* Legal: an annotation interface can be a
member interface */
void m() {
@interface A2 {} /* Illegal: an annotation interface cannot
be a local interface */
class D {
@interface A3 {} /* Illegal: an annotation interface
cannot be specified anywhere within
the body of local class D */
class E {
@interface A4 {}
/* Illegal: an annotation interface cannot be
specified anywhere within the body of local class
D, even as a member of a class E nested in D */
}
}
}
}
Интерфейс аннотации никогда не является обобщенным (§9.1.2).
В отличие от обычного объявления интерфейса, объявление интерфейса аннотации не может объявлять переменные типов, в силу производства AnnotationTypeDeclaration.
Прямой тип суперинтерфейса интерфейса аннотации всегда java.lang.annotation.Annotation (§9.1.3).
В отличие от обычного объявления интерфейса, объявление интерфейса аннотации не может выбрать прямой тип суперинтерфейса через extends, в силу производства AnnotationTypeDeclaration.
Следствие из того, что объявление интерфейса аннотации не указывает явно тип суперинтерфейса через extends, состоит в том, что подинтерфейс интерфейса аннотации никогда сам по себе не является интерфейсом аннотации, так как в объявлении подинтерфейса обязательно используется extends. Аналогично, java.lang.annotation.Annotation сам по себе не является интерфейсом аннотации.
Интерфейс аннотации наследует несколько методов от java.lang.annotation.Annotation, включая неявно объявленные методы, соответствующие методам экземпляров Object (§9.2), но эти методы не определяют элементы интерфейса аннотации (§9.6.1).
Поскольку эти методы не определяют элементы интерфейса аннотации, их нельзя использовать в аннотациях, соответствующих интерфейсу аннотации (§9.7). Без этого правила мы не могли бы гарантировать, что элементы имели бы типы, представимые в аннотациях, или что для них были бы доступны методы-аксессоры.
Тело объявления интерфейса аннотации может содержать объявления методов, каждый из которых определяет элемент интерфейса аннотации. Интерфейс аннотации не имеет элементов, кроме тех, которые определены методами, явно объявленными в объявлении интерфейса аннотации.
Следующее производство из §4.3 показано здесь для удобства:
В силу приведенной выше грамматики, объявление метода в объявлении интерфейса аннотации не может иметь формальных параметров, параметров типа или throws; и не может быть private, default или static. Таким образом, интерфейс аннотации не может иметь такое же разнообразие методов, как обычный интерфейс. Обратите внимание, что интерфейс аннотации всё ещё может унаследовать метод по умолчанию от своего неявного суперинтерфейса, java.lang.annotation.Annotation, хотя такого метода по умолчанию не существует в Java SE 17.
По соглашению, единственными модификаторами, которые должны быть присутствовать при объявлении элемента интерфейса аннотации, являются аннотации.
Тип возвращаемого значения метода, объявленного в теле интерфейса аннотации, должен быть одним из следующих, иначе возникает ошибка компиляции:
Это правило исключает элементы со вложенными массивами, такие как:
@interface Verboten {
String[][] value();
}
Объявление метода, возвращающего массив, может поместить пару квадратных скобок, обозначающих тип массива, после пустого списка формальных параметров. Эта синтаксическая конструкция поддерживается для совместимости с ранними версиями Java. Очень настоятельно рекомендуется не использовать этот синтаксис в новом коде.
Если любой метод, объявленный в интерфейсе аннотации, имеет подпись, эквивалентную с точки зрения переопределения (§8.4.2), методу 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-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
}
Пример 9.6.1-2. Объявление маркерного интерфейса аннотации
Следующее объявление интерфейса аннотации определяет маркерный интерфейс аннотации:
/**
* An annotation with this type indicates that the
* specification of the annotated API element is
* preliminary and subject to change.
*/
@interface Preliminary {}
Пример 9.6.1-3. Объявления интерфейсов аннотации с одним элементом
Соглашение, что интерфейс аннотации с одним элементом определяет элемент под названием value, проиллюстрировано в следующем объявлении интерфейса аннотации:
/**
* 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();
}
Следующее объявление интерфейса аннотации содержит элемент, тип которого является типом интерфейса аннотации:
/**
* Indicates the author of the annotated program element.
*/
@interface Author {
Name value();
}
/**
* A person's name. This annotation interface is not
* designed to be used directly to annotate program elements,
* but to define elements of other annotation interfaces.
*/
@interface Name {
String first();
String last();
}
Грамматика для объявлений интерфейсов аннотации допускает другие объявления членов, помимо объявлений методов. Например, можно объявить вложенный класс перечисления для использования элементом интерфейса аннотации:
@interface Quality {
enum Level { BAD, INDIFFERENT, GOOD }
Level value();
}
Элемент интерфейса аннотации может иметь значение по умолчанию, указанное добавлением ключевого слова default и значения к объявлению метода, которое определяет элемент.
default Значение элемента Следующие производства из §9.7.1 показаны здесь для удобства:
Если тип элемента не соответствует (§9.7) указанному значению по умолчанию, возникает ошибка компиляции.
Значения по умолчанию не компилируются в аннотации, а применяются динамически во время чтения аннотаций. Таким образом, изменение значения по умолчанию влияет на аннотации даже в классах, которые были скомпилированы до внесения изменений (предполагая, что в этих аннотациях отсутствует явное значение для элемента по умолчанию).
Пример 9.6.2-1. Объявление интерфейса аннотации со значениями по умолчанию
Здесь представлена уточненная аннотация RequestForEnhancement из §9.6.1:
@interface RequestForEnhancement {
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]";
}
Интерфейс аннотации A является повторяемым, если его объявление (мета-)аннотировано аннотацией @Repeatable (§9.6.4.8), элемент value которой указывает на содержащий интерфейс аннотации для A.
Интерфейс аннотации AC является содержащим интерфейсом аннотации для A, если выполняются все следующие условия:
-
AC объявляет метод
value(), тип возвращаемого значения которого A[]. -
Все методы, объявленные AC, кроме
value(), имеют значение по умолчанию. -
AC сохраняется как минимум так же долго, как и A, где сохранение выражается явно или неявно аннотацией
@Retention(§9.6.4.2). В частности:-
Если срок хранения AC —
java.lang.annotation.RetentionPolicy.SOURCE, то срок хранения A —java.lang.annotation.RetentionPolicy.SOURCE. -
Если срок хранения AC —
java.lang.annotation.RetentionPolicy.CLASS, то срок хранения A — либоjava.lang.annotation.RetentionPolicy.CLASS, либоjava.lang.annotation.RetentionPolicy.SOURCE. -
Если срок хранения AC —
java.lang.annotation.RetentionPolicy.RUNTIME, то срок хранения A —java.lang.annotation.RetentionPolicy.SOURCE,java.lang.annotation.RetentionPolicy.CLASSилиjava.lang.annotation.RetentionPolicy.RUNTIME.
-
-
A применима как минимум к тем же видам элементов программы, что и AC (§9.6.4.1). В частности, если виды элементов программы, к которым применима A, обозначаются множеством
m1, а виды элементов программы, к которым применима AC, обозначаются множествомm2, то каждый вид вm2должен встречаться вm1, за исключением следующих случаев:-
Если вид в
m2—java.lang.annotation.ElementType.ANNOTATION_TYPE, то хотя бы один изjava.lang.annotation.ElementType.ANNOTATION_TYPEилиjava.lang.annotation.ElementType.TYPEилиjava.lang.annotation.ElementType.TYPE_USEдолжен встречаться вm1. -
Если вид в
m2—java.lang.annotation.ElementType.TYPE, то хотя бы один изjava.lang.annotation.ElementType.TYPEилиjava.lang.annotation.ElementType.TYPE_USEдолжен встречаться вm1. -
Если вид в
m2—java.lang.annotation.ElementType.TYPE_PARAMETER, то хотя бы один изjava.lang.annotation.ElementType.TYPE_PARAMETERилиjava.lang.annotation.ElementType.TYPE_USEдолжен встречаться вm1.
Этот пункт реализует политику, согласно которой интерфейс аннотации может быть повторяемым только для некоторых видов элементов программы, к которым он применим.
-
-
Если объявление A содержит (мета-)аннотацию, соответствующую
java.lang.annotation.Documented, то объявление AC должно содержать (мета-)аннотацию, соответствующуюjava.lang.annotation.Documented.Обратите внимание, что AC может быть
@Documented, в то время как A — нет@Documented. -
Если объявление A содержит (мета-)аннотацию, соответствующую
java.lang.annotation.Inherited, то объявление AC должно содержать (мета-)аннотацию, соответствующуюjava.lang.annotation.Inherited.Обратите внимание, что AC может быть
@Inherited, в то время как A — нет@Inherited.
Если интерфейс аннотации A (мета-)аннотирован аннотацией @Repeatable, элемент value которой указывает на тип, не являющийся содержащим интерфейсом аннотации для A, возникает ошибка на этапе компиляции.
Пример 9.6.3-1. Некорректный содержащий интерфейс аннотации
Рассмотрим следующие объявления:
import java.lang.annotation.Repeatable;
@Repeatable(FooContainer.class)
@interface Foo {}
@interface FooContainer { Object[] value(); }
Компиляция объявления Foo приводит к ошибке на этапе компиляции, так как Foo пытается указать FooContainer в качестве содержащего интерфейса аннотации, но FooContainer на самом деле не является содержащим интерфейсом аннотации для Foo. (Тип возврата FooContainer.value() не равен Foo[].)
Аннотация @Repeatable не может быть повторённой, поэтому только один содержащий интерфейс аннотации может быть указан интерфейсом аннотации, допускающим повторение.
Разрешение указания более одного содержащего интерфейса аннотации приведёт к нежелательному выбору на этапе компиляции, когда несколько аннотаций повторяемого интерфейса аннотации логически заменяются контейнерной аннотацией (§9.7.5).
Интерфейс аннотации может быть содержащим интерфейсом аннотации для не более чем одного интерфейса аннотации.
Это подразумевается требованием, что если объявление интерфейса аннотации A указывает содержащий интерфейс аннотации AC, то метод value() AC имеет тип возвращаемого значения, включающий A, а именно A[].
Интерфейс аннотации не может указывать сам себя в качестве своего содержащего интерфейса аннотации.
Это подразумевается требованием к методу value() содержащего интерфейса аннотации. В частности, если интерфейс аннотации A указывает сам себя (через @Repeatable) в качестве своего содержащего интерфейса аннотации, то тип возврата метода value() A должен быть A[]; но это приведёт к ошибке на этапе компиляции, так как интерфейс аннотации не может ссылаться на себя в своих элементах (§9.6.1). В более общем смысле, два интерфейса аннотации не могут указывать друг друга в качестве своих содержащих интерфейсов аннотации, так как циклические объявления интерфейсов аннотаций недопустимы.
Интерфейс аннотации AC может быть содержащим интерфейсом аннотации для какого-то интерфейса аннотации A, одновременно имея свой собственный содержащий интерфейс аннотации SC. То есть, содержащий интерфейс аннотации может сам быть интерфейсом аннотации, допускающим повторение.
Пример 9.6.3-2. Ограничение повторения аннотаций
Аннотация, интерфейс объявления которой указывает на цель java.lang.annotation.ElementType.TYPE, может появляться как минимум в таком же количестве мест, что и аннотация, интерфейс объявления которой указывает на цель java.lang.annotation.ElementType.ANNOTATION_TYPE. Например, при следующих объявлениях интерфейсов аннотаций с возможностью повторения и содержащих аннотации:
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@interface Foo {}
@Target(ElementType.ANNOTATION_TYPE)
@interface FooContainer {
Foo[] value();
}
@Foo может появляться на любом объявлении класса или интерфейса, в то время как @FooContainer может появляться только на объявлениях интерфейсов аннотаций. Поэтому следующее объявление интерфейса аннотации является допустимым:
@Foo @Foo
@interface Anno {}
в то время как следующее объявление интерфейса недопустимо:
@Foo @Foo
interface Intf {}
Более широко, если Foo — это интерфейс аннотации с возможностью повторения, а FooContainer — это интерфейс содержащей аннотации, тогда:
-
Если
Fooне имеет мета-аннотацию@Target, аFooContainerне имеет мета-аннотацию@Target, то@Fooможет повторяться на любом элементе программы, поддерживающем аннотации. -
Если
Fooне имеет мета-аннотацию@Target, ноFooContainerимеет мета-аннотацию@Target, то@Fooможет повторяться только на элементах программы, где может появляться@FooContainer. -
Если
Fooимеет мета-аннотацию@Target, то по мнению разработчиков языка программирования Java,FooContainerдолжно быть объявлено с учетом применимостиFoo. В частности, виды элементов программы, где может появлятьсяFooContainer, должны логически совпадать или быть подмножеством видов, которые применим дляFoo.Например, если
Fooприменим к объявлениям полей и методов, тоFooContainerможет законно служить интерфейсом содержащей аннотации дляFoo, еслиFooContainerприменим только к объявлениям полей (что предотвратит повторение@Fooна объявлениях методов). Но еслиFooContainerприменим только к объявлениям формальных параметров, тоFooContainerбыл плохим выбором интерфейса содержащей аннотации дляFoo, потому что@FooContainerне может быть неявно объявлено на некоторых элементах программы, где@Fooповторяется.Аналогично, если
Fooприменим к объявлениям полей и методов, тоFooContainerне может законно служить интерфейсом содержащей аннотации дляFoo, еслиFooContainerприменим к объявлениям полей и параметров. Хотя было бы возможно взять пересечение элементов программы и сделатьFooповторяемым только для объявлений полей, наличие дополнительных элементов программы дляFooContainerуказывает на то, чтоFooContainerне был разработан как интерфейс содержащей аннотации дляFoo. Поэтому дляFooбыло бы опасно положиться на него.
Пример 9.6.3-3. Интерфейс содержащей аннотации с возможностью повторения
Следующие объявления допустимы:
import java.lang.annotation.Repeatable;
// Foo: Repeatable annotation interface
@Repeatable(FooContainer.class)
@interface Foo { int value(); }
// FooContainer: Containing annotation interface of Foo
// Also a repeatable annotation interface itself
@Repeatable(FooContainerContainer.class)
@interface FooContainer { Foo[] value(); }
// FooContainerContainer: Containing annotation interface
// of FooContainer
@interface FooContainerContainer { FooContainer[] value(); }
Таким образом, аннотация, интерфейс которой является интерфейсом содержащей аннотации, может сама повторяться:
@FooContainer({@Foo(1)}) @FooContainer({@Foo(2)})
class Test {}
Интерфейс аннотации, который является как повторяемым, так и содержащим, подчиняется правилам смешивания аннотаций интерфейса с возможностью повторения с аннотациями содержащего интерфейса (§9.7.5). Например, нельзя написать несколько аннотаций @Foo рядом с несколькими аннотациями @FooContainer, также нельзя написать несколько аннотаций @FooContainer рядом с несколькими аннотациями @FooContainerContainer. Однако, если интерфейс аннотации FooContainerContainer сам является повторяемым, то можно написать несколько аннотаций @Foo рядом с несколькими аннотациями @FooContainerContainer.
В API Java SE Platform определены несколько интерфейсов аннотаций. Некоторые из предопределённых интерфейсов аннотаций имеют специальную семантику в языке программирования Java и требуют специального поведения со стороны компилятора Java, как указано в данном разделе. В данном разделе не приводится полное описание предопределённых интерфейсов аннотаций; для получения полной информации следует обратиться к документации API Java SE Platform (§1.4).
Аннотация типа java.lang.annotation.Target используется в объявлении интерфейса аннотации A для указания контекстов, в которых A является применимым. java.lang.annotation.Target содержит единственный элемент, value, типа java.lang.annotation.ElementType[], для указания контекстов.
Интерфейсы аннотаций могут быть применимы в контекстах объявлений, где аннотации применяются к объявлениям, или в контекстах типов, где аннотации применяются к типам, используемым в объявлениях и выражениях.
Существует десять контекстов объявления, каждый из которых соответствует константе перечисления java.lang.annotation.ElementType:
-
Объявления модулей (§7.7)
Соответствует
java.lang.annotation.ElementType.MODULE -
Объявления пакетов (§7.4.1)
Соответствует
java.lang.annotation.ElementType.PACKAGE -
Объявления классов (включая объявления перечислений и записей) и объявления интерфейсов (включая объявления интерфейсов аннотаций) (§8.1.1, §8.5, §8.9, §8.10, §9.1.1, §9.5, §9.6)
Соответствует
java.lang.annotation.ElementType.TYPEКроме того, объявления интерфейсов аннотаций соответствуют
java.lang.annotation.ElementType.ANNOTATION_TYPE -
Объявления методов (включая элементы интерфейсов аннотаций) (§8.4.3, §9.4, §9.6.1)
Соответствует
java.lang.annotation.ElementType.METHOD -
Объявления конструкторов (§8.8.3)
Соответствует
java.lang.annotation.ElementType.CONSTRUCTOR -
Объявления параметров типа для обобщённых классов, интерфейсов, методов и конструкторов (§8.1.2, §9.1.2, §8.4.4, §8.8.4)
Соответствует
java.lang.annotation.ElementType.TYPE_PARAMETER -
Объявления полей (включая константы перечислений) (§8.3.1, §9.3, §8.9.1)
Соответствует
java.lang.annotation.ElementType.FIELD -
Объявления формальных и исключающих параметров (§8.4.1, §9.4, §14.20)
Соответствует
java.lang.annotation.ElementType.PARAMETER -
Объявления локальных переменных в операторах (§14.4.2, §14.14.1, §14.14.2, §14.20.3) и в шаблонах (§14.30.1)
Соответствует
java.lang.annotation.ElementType.LOCAL_VARIABLE -
Объявления компонентов записей (§8.10.1)
Соответствует
java.lang.annotation.ElementType.RECORD_COMPONENT
Существует 17 контекстов типов (§4.11), все они представлены константой перечисления TYPE_USE из java.lang.annotation.ElementType.
Ошибка компиляции, если та же константа перечисления встречается более одного раза в элементе value аннотации типа java.lang.annotation.Target.
Если аннотация типа java.lang.annotation.Target отсутствует в объявлении интерфейса аннотации A, то A применима во всех контекстах объявлений и ни в одном контексте типов.
Аннотации могут присутствовать только в исходном коде или в двоичном представлении класса или интерфейса. Аннотация, присутствующая в двоичном представлении, может или не может быть доступна во время выполнения через библиотеки рефлексии Java SE Platform. Интерфейс аннотации java.lang.annotation.Retention используется для выбора между этими возможностями.
Если аннотация a соответствует интерфейсу аннотации A, и A имеет (мета-)аннотацию 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, еслиaне аннотирует объявление локальной переменной илиaне аннотирует объявление формального параметра лямбда-выражения.Аннотация в объявлении локальной переменной или в объявлении формального параметра лямбда-выражения никогда не сохраняется в двоичном представлении. Напротив, аннотация в типе локальной переменной или в типе формального параметра лямбда-выражения сохраняется в двоичном представлении, если интерфейс аннотации указывает соответствующую политику сохранения.
Обратите внимание, что не является ошибкой, если интерфейс аннотации помечен (мета-)аннотациями
@Target(java.lang.annotation.ElementType.LOCAL_VARIABLE)и@Retention(java.lang.annotation.RetentionPolicy.CLASS)или@Retention(java.lang.annotation.RetentionPolicy.RUNTIME).Если
mимеет элемент со значениемjava.lang.annotation.RetentionPolicy.RUNTIME, то библиотеки рефлексии Java SE Platform должны сделатьaдоступным во время выполнения.
Если у A нет (мета-)аннотации, соответствующей java.lang.annotation.Retention, то компилятор Java должен рассматривать A так, как будто у неё есть (мета-)аннотация, соответствующая java.lang.annotation.Retention с элементом, значением которого является java.lang.annotation.RetentionPolicy.CLASS.
Программисты иногда перегружают объявление метода, когда намереваются его переопределить, что приводит к скрытым проблемам. Интерфейс аннотаций Override поддерживает раннее обнаружение таких проблем.
Классический пример касается метода equals. Программисты пишут следующее в классе Foo:
public boolean equals(Foo that) { ... }
когда они намереваются написать:
public boolean equals(Object that) { ... }
Это вполне законно, но класс Foo наследует реализацию equals от Object, что может вызвать некоторые скрытые ошибки.
Если объявление метода в классе или интерфейсе Q аннотировано с помощью @Override, то одно из следующих трёх условий должно быть истинным, иначе произойдёт ошибка компиляции:
Это поведение отличается от Java SE 5.0, где @Override вызывало ошибку компиляции только в том случае, если оно применялось к методу, реализующему метод из суперинтерфейса, который также не был присутствующим в суперклассе.
Положение об переопределении метода public из Object мотивировано использованием @Override в интерфейсе. Рассмотрим следующие объявления:
class Foo { @Override public int hashCode() {..} }
interface Bar { @Override int hashCode(); }
Использование @Override в объявлении класса является законным в соответствии с первым положением, поскольку Foo.hashCode переопределяет метод Object.hashCode из интерфейса Foo.
Для объявления интерфейса следует учесть, что интерфейс имеет public abstract члены, которые соответствуют public членам Object (§9.2). Если интерфейс выбирает явное объявление этих членов (то есть объявление членов, эквивалентных переопределению public методов Object), то интерфейс считается переопределяющим их, и использование @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() {..} }
Положение о классе-записи обусловлено особым значением @Override в объявлении записи. А именно, он может использоваться для указания того, что объявление метода является методом-аксессором для компонента записи. Рассмотрим следующее объявление записи:
record Roo(int x) {
@Override
public int x() {
return Math.abs(x);
}
}
Использование @Override для метода-аксессора int x() гарантирует, что если компонент записи x будет изменён или удалён, то соответствующий метод-аксессор также должен быть изменён или удалён.
Компиляторы Java всё чаще способны выдавать полезные предупреждения "в стиле линтера". Для поощрения использования таких предупреждений должен быть способ отключения предупреждения в части программы, когда программист знает, что предупреждение необоснованно.
Интерфейс аннотаций SuppressWarnings поддерживает управление программистом предупреждениями, которые иначе выдаёт компилятор Java. Он определяет один элемент — массив String.
Если объявление аннотировано с помощью @SuppressWarnings(value
= {S1, ..., Sk}), то компилятор Java должен подавлять (т.е. не сообщать) любое предупреждение, указанное одним из S1 ... Sk, если это предупреждение было бы сгенерировано в результате аннотированного объявления или любой его части.
Язык программирования Java определяет четыре вида предупреждений, которые могут быть указаны с помощью @SuppressWarnings:
-
Предупреждения о неявных типах (§4.8, §5.1.6, §5.1.9, §8.4.1, §8.4.8.3, §15.12.4.2, §15.13.2, §15.27.3) указываются строкой "
unchecked". -
Предупреждения о устаревших методах (§9.6.4.6) указываются строкой "
deprecation". -
Предупреждения об удалённых методах (§9.6.4.6) указываются строкой "
removal". -
Предупреждения о предварительных версиях (§1.5) указываются строкой "
preview".
Любая другая строка указывает нестандартное предупреждение. Компилятор Java должен игнорировать любую такую строку, которую он не распознаёт.
Производители компиляторов рекомендуют документировать поддерживаемые ими строки для @SuppressWarnings и сотрудничать, чтобы обеспечить распознавание одних и тех же строк во всех компиляторах.
Программисты иногда отговариваются от использования определённых элементов программы (модулей, классов, интерфейсов, полей, методов и конструкторов), так как они считаются опасными или потому что существует лучшая альтернатива. Интерфейс аннотаций Deprecated позволяет компилятору предупреждать об использовании этих элементов программы.
Элемент программы устаревший — это модуль, класс, интерфейс, поле, метод или конструктор, объявление которого аннотировано с помощью @Deprecated. Способ, которым элемент программы устарел, зависит от значения элемента forRemoval аннотации:
-
Если
forRemoval=false(по умолчанию), то элемент программы обычно устарел.Элемент программы, обычно устаревший, не предполагается к удалению в будущих версиях, но программисты тем не менее должны перейти к другому способу использования.
-
Если
forRemoval=true, то элемент программы окончательно устарел.Элемент программы, окончательно устаревший, предполагается к удалению в будущей версии. Программисты должны прекратить его использование, или рискуют столкнуться с несовместимостью исходного и бинарного кода (§13.2) при обновлении до более новой версии.
Компилятор Java должен генерировать предупреждение об устаревании, когда используется обычно устаревший элемент программы (переопределён, вызван или упомянут по имени) в объявлении элемента программы (явном или неявном), за исключением следующих случаев:
-
Использование находится внутри объявления, которое само по себе устарело, будь то обычно или окончательно; или
-
Использование находится внутри объявления, которое аннотировано для подавления предупреждений об устаревании (§9.6.4.5); или
-
Объявление, где происходит использование, и объявление обычно устаревшего элемента программы оба находятся в одном внешнем классе; или
-
Использование находится в объявлении
import, которое импортирует обычно устаревший класс, интерфейс или член; или -
Использование находится в директиве
exportsилиopens(§7.7.2).
Компилятор Java должен генерировать предупреждение об удалении, когда используется окончательно устаревший элемент программы (переопределён, вызван или упомянут по имени) в объявлении элемента программы (явном или неявном), за исключением следующих случаев:
-
Использование находится внутри объявления, которое аннотировано для подавления предупреждений об удалении (§9.6.4.5); или
-
Объявление, где происходит использование, и объявление окончательно устаревшего элемента программы оба находятся в одном внешнем классе; или
-
Использование находится в объявлении
import, которое импортирует окончательно устаревший класс, интерфейс или член; или -
Использование находится в директиве
exportsилиopens.
Окончательное устаревание достаточно срочное, что использование окончательно устаревшего элемента вызовет предупреждение об удалении даже если используемый элемент сам устарел, так как нет гарантии, что оба элемента будут удалены одновременно. Чтобы отклонить предупреждение, но продолжить использование элемента, программист должен вручную принять риск с помощью аннотации @SuppressWarnings.
Предупреждение об устаревании или предупреждение об удалении не генерируется, если:
-
используется локальная переменная или формальный параметр (ссылка по имени), даже если объявление локальной переменной или формального параметра аннотировано с помощью
@Deprecated. -
используется имя пакета (ссылка через полное имя типа, или объявление
import, или директиваexportsилиopens), даже если объявление пакета аннотировано с помощью@Deprecated. -
имя модуля используется директивой qualified
exportsилиopens, даже если объявление модуля-друга аннотировано с помощью@Deprecated.
Объявление модуля, которое экспортирует или открывает пакет, обычно контролируется тем же программистом или командой, которые контролируют объявление пакета. Поэтому мало пользы в предупреждении, что объявление пакета аннотировано с помощью @Deprecated, когда пакет экспортируется или открывается объявлением модуля. В отличие от этого, объявление модуля, которое экспортирует или открывает пакет дружественному модулю, обычно не контролируется тем же программистом или командой, которые контролируют дружественный модуль. Простое экспорт или открытие пакета не делает объявление модуля зависимым от дружественного модуля, поэтому мало смысла в предупреждении, если дружественный модуль устарел; программист объявления модуля почти всегда хотел бы подавить такое предупреждение.
Единственное неявное объявление, которое может вызвать предупреждение об устаревании или предупреждение об удалении, — это аннотация контейнера (§9.7.5). Иначе говоря, если T — это повторяющийся интерфейс аннотации, а TC — это содержащий интерфейс аннотации, и TC устарел, то повторение аннотации @T вызовет предупреждение. Предупреждение связано с неявной аннотацией контейнера @TC. Сильно не рекомендуется устаревать интерфейс содержащей аннотации без устаревания соответствующего повторяющегося интерфейса аннотации.
Параметр с переменным числом аргументов с типом элемента, который нельзя реифицировать (§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 или private, аннотировано аннотацией @SafeVarargs.
Так как @SafeVarargs применим только к методам static, final и/или private методам экземпляра и конструкторам, аннотация не может использоваться там, где происходит переопределение метода. Наследование аннотаций работает только для аннотаций на классах (а не на методах, интерфейсах или конструкторах), поэтому аннотация @SafeVarargs-стиля не может передаваться через методы экземпляров в классах или через интерфейсы.
Интерфейс аннотации java.lang.annotation.Repeatable используется в объявлении повторяющегося интерфейса аннотации для указания его содержащего интерфейса аннотации (§9.6.3).
Обратите внимание, что мета-аннотация @Repeatable в объявлении A, указывающая на AC, недостаточно для того, чтобы AC стал содержащим интерфейсом аннотации для A. Есть много правил корректности для AC, чтобы он считался содержащим интерфейсом аннотации для A.
Интерфейс аннотаций FunctionalInterface используется для обозначения того, что интерфейс предназначен быть функциональным интерфейсом (§9.8). Это облегчает раннее обнаружение неподходящих объявлений методов, появляющихся в интерфейсе или унаследованных им, если интерфейс предназначен быть функциональным.
Если объявление интерфейса аннотировано с помощью @FunctionalInterface, но фактически не является функциональным интерфейсом, то это ошибка времени компиляции.
Поскольку некоторые интерфейсы являются функциональными по умолчанию, не обязательно или желательно, чтобы все объявления функциональных интерфейсов были аннотированы с помощью @FunctionalInterface.
Аннотация — это маркер, который связывает информацию с элементом программы, но не имеет эффекта во время выполнения. Аннотация обозначает конкретный экземпляр интерфейса аннотаций (§9.6) и обычно предоставляет значения для элементов этого интерфейса.
Существует три вида аннотаций. Первый вид является наиболее общим, в то время как другие виды — это просто сокращения для первого вида.
Нормальные аннотации описаны в §9.7.1, маркерные аннотации в §9.7.2, а аннотации с одним элементом в §9.7.3. Аннотации могут появляться в различных синтаксических местах программы, как описано в §9.7.4. Количество аннотаций одного и того же интерфейса, которые могут появиться в одном месте, определяется объявлением интерфейса, как описано в §9.7.5.
Обычная аннотация указывает имя интерфейса аннотации и, необязательно, список пар элемент-значение через запятую. Каждая пара содержит значение элемента, которое ассоциируется с элементом интерфейса аннотации (§9.6.1).
Обратите внимание, что знак @ (@) является отдельным лексемой (§3.11). Разрешается вставлять пробелы между ним и TypeName, но это не рекомендуется в плане стиля.
TypeName указывает интерфейс аннотации, соответствующий аннотации. Аннотация считается «от» этого интерфейса.
TypeName должен указывать на доступный интерфейс аннотации (§6.6), иначе произойдет ошибка на этапе компиляции.
Идентификатор в паре элемент-значение должен быть простым именем одного из элементов (то есть методов) интерфейса аннотации, иначе произойдет ошибка на этапе компиляции.
Тип возвращаемого значения этого метода определяет тип элемента пары элемент-значение.
Если тип элемента является типом массива, то не требуется использовать фигурные скобки для указания значения элемента в паре элемент-значение. Если значение элемента не является ИнициализаторМассиваЗначенийЭлемента, то значение массива, содержащее единственный элемент — это значение элемента, ассоциируется с элементом. Если значение элемента является ИнициализаторМассиваЗначенийЭлемента, то значение массива, представленное ИнициализаторМассиваЗначенийЭлемента, ассоциируется с элементом.
Ошибка компиляции произойдёт, если тип элемента не совместим со значением элемента. Тип элемента T совместим со значением элемента v тогда и только тогда, когда выполняется одно из следующих условий:
-
T — это тип массива E
[], и выполняется одно из следующих условий:-
Если
vявляется УсловноеВыражение или Аннотация, тоvсовместимо с E; или -
Если
vявляется ИнициализаторМассиваЗначенийЭлемента, то каждое значение элемента, котороеvсодержит, совместимо с E.ИнициализаторМассиваЗначенийЭлемента похож на обычный инициализатор массива (§10.6), за исключением того, что ИнициализаторМассиваЗначенийЭлемента может синтаксически содержать аннотации, а также выражения и вложенные инициализаторы. Однако вложенные инициализаторы не являются семантически допустимыми в ИнициализаторМассиваЗначенийЭлемента, так как они никогда не совместимы с элементами типа массива в объявлениях интерфейсов аннотаций (вложенные типы массивов запрещены).
-
-
T — не тип массива, и тип
vсовместим с T по присваиванию (§5.2), и:
Обратите внимание, что если T не является типом массива или интерфейсом аннотации, значение элемента должно быть УсловноеВыражение (§15.25). Использование УсловноеВыражение вместо более общей конструкции, например, Выражение, — это синтаксический трюк, призванный предотвратить использование выражений присваивания в качестве значений элементов. Поскольку выражение присваивания не является константным выражением, оно не может быть совместимым значением элемента для элемента примитивного типа или String.
Ошибка компиляции произойдёт, если обычная аннотация не содержит пары элемент-значение для каждого элемента соответствующего интерфейса аннотации, за исключением элементов с значениями по умолчанию.
Обычная аннотация может, но не обязана, содержать пары элемент-значение для элементов со значениями по умолчанию.
Принято, хотя и не обязательно, что пары элемент-значение в аннотации представлены в том же порядке, что и соответствующие элементы в объявлении интерфейса аннотации.
Аннотация на объявлении интерфейса аннотации называется мета-аннотацией.
Аннотация интерфейса A может появиться в качестве мета-аннотации в объявлении самого интерфейса A. Более общо, циклы в транзитивном замыкании отношения «аннотирует» разрешены.
Например, допустимо аннотировать объявление интерфейса аннотации S мета-аннотацией интерфейса T и аннотировать объявление T собственной мета-аннотацией интерфейса S. Предопределенные интерфейсы аннотаций (§9.6.4) содержат несколько таких циклов.
Пример 9.7.1-1. Обычные аннотации
Вот пример обычной аннотации, использующей интерфейс аннотации из §9.6.1:
@RequestForEnhancement(
id = 2868724,
synopsis = "Provide time-travel functionality",
engineer = "Mr. Peabody",
date = "4/1/2004"
)
public static void travelThroughTime(Date destination) { ... }
Вот пример обычной аннотации, которая использует значения по умолчанию, используя интерфейс аннотации из §9.6.2:
@RequestForEnhancement(
id = 4561414,
synopsis = "Balance the federal budget"
)
public static void balanceFederalBudget() {
throw new UnsupportedOperationException("Not implemented");
}
Аннотация-метка — это сокращение, предназначенное для использования с интерфейсами аннотаций-меток (§9.6.1).
@ TypeName Это сокращение для обычной аннотации:
@TypeName()
Допустимо использовать аннотации-метки для интерфейсов аннотаций с элементами, при условии, что все элементы имеют значения по умолчанию (§9.6.2).
Пример 9.7.2-1. Аннотации-метки
Вот пример, использующий интерфейс аннотации-метки Preliminary из §9.6.1:
@Preliminary public class TimeTravel { ... }
Аннотация с единственным элементом — это сокращение, предназначенное для использования с интерфейсами аннотаций с единственным элементом (§9.6.1).
Это сокращение для обычной аннотации:
@TypeName(value = ElementValue)
Разрешено использовать аннотации с единственным элементом для интерфейсов аннотаций с несколькими элементами, при условии, что один элемент имеет имя value, а все остальные элементы имеют значения по умолчанию (§9.6.2).
Пример 9.7.3-1. Аннотации с единственным элементом
Следующие аннотации используют интерфейсы аннотаций с единственным элементом из §9.6.1.
Вот пример аннотации с единственным элементом:
@Copyright("2002 Yoyodyne Propulsion Systems, Inc.")
public class OscillationOverthruster { ... }
Вот пример аннотации с единственным элементом, содержащей массив значений:
@Endorsers({"Children", "Unscrupulous dentists"})
public class Lollipop { ... }
Вот пример аннотации с единственным элементом, содержащей массив значений (обратите внимание, что фигурные скобки опущены):
@Endorsers("Epicurus")
public class Pleasure { ... }
Вот пример аннотации с единственным элементом, содержащей элемент типа 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 { ... }
Вот пример аннотации с единственным элементом, содержащей обычную аннотацию:
@Author(@Name(first = "Joe", last = "Hacker"))
public class BitTwiddle { ... }
Вот пример аннотации с единственным элементом, использующей перечисление (enum), определённое внутри объявления интерфейса аннотации:
@Quality(Quality.Level.GOOD)
public class Karma { ... }
Аннотация объявления — это аннотация, которая применяется к объявлению и чьё интерфейс аннотации применим в контексте объявления (§9.6.4.1), представленному этим объявлением; или аннотация, которая применяется к объявлению класса, интерфейса или параметра типа, и чьё интерфейс аннотации применим в контекстах типов (§4.11).
Аннотация типа — это аннотация, которая применяется к типу (или любой его части) и чьё интерфейс аннотации применим в контекстах типов.
Например, учитывая объявление поля:
@Foo int f;
@Foo является аннотацией объявления для f, если Foo мета-аннотирована @Target(ElementType.FIELD), и аннотацией типа для int, если Foo мета-аннотирована @Target(ElementType.TYPE_USE). Возможно, что @Foo одновременно является аннотацией объявления и аннотацией типа.
Аннотации типа могут применяться к типу массива или любому его компоненту (§10.1). Например, предположим, что A, B и C являются интерфейсами аннотаций, мета-аннотированными @Target(ElementType.TYPE_USE), а затем при объявление поля:
@C int @A [] @B [] f;
@A применяется к типу массива int[][], @B применяется к его компоненту int[], а @C применяется к элементу типа int. Для получения дополнительных примеров см. §10.2.
Важным свойством этого синтаксиса является то, что в двух объявлениях, отличающихся только количеством уровней массива, аннотации слева от типа относятся к одному и тому же типу. Например, @C применяется к типу int во всех следующих объявлениях:
@C int f; @C int[] f; @C int[][] f;
Принято, хотя и не обязательно, записывать аннотации объявления перед всеми другими модификаторами, а аннотации типа — непосредственно перед типом, к которому они применяются.
Возможно, что аннотация может появиться в синтаксической позиции программы, где она могла бы примениться как к объявлению, так и к типу, или к обоим. Это может произойти в любом из шести контекстов объявления, где модификаторы непосредственно предшествуют типу объявляемого сущности:
-
Объявления методов (включая элементы интерфейсов аннотаций)
-
Объявления конструкторов
-
Объявления полей (включая константы перечислений)
-
Объявления параметров формальных и исключений
-
Объявления локальных переменных
-
Объявления компонентов записи
Грамматика языка программирования Java однозначно рассматривает аннотации в этих позициях как модификаторы объявления (§8.3), но это чисто синтаксический вопрос. Применяется ли аннотация к объявлению или к типу объявляемой сущности — и, следовательно, является ли аннотация аннотацией объявления или аннотацией типа — зависит от применимости интерфейса аннотации:
-
Если интерфейс аннотации применим в контексте объявления, соответствующем объявлению, и не применим в контекстах типов, то аннотация считается применимой только к объявлению.
-
Если интерфейс аннотации применим в контекстах типов, но не применим в контексте объявления, соответствующем объявлению, то аннотация считается применимой только к типу, наиболее близкому к аннотации.
-
Если интерфейс аннотации применим в контексте объявления, соответствующем объявлению, и в контекстах типов, то аннотация считается применимой как к объявлению, так и к типу, наиболее близкому к аннотации.
В втором и третьем случаях выше, тип, который наиболее близок к аннотации определяется следующим образом:
-
Если аннотация появляется перед объявлением метода
voidили объявлением локальной переменной, использующейvar(§14.4), то ближайшего типа нет. Если интерфейс аннотации считается применимым только к типу, который наиболее близок к аннотации, возникает ошибка времени компиляции. -
Если аннотация появляется перед объявлением конструктора, то ближайший тип — это тип вновь созданного объекта. Тип вновь созданного объекта — это полностью квалифицированное имя типа, непосредственно содержащего объявление конструктора. В пределах этого полностью квалифицированного имени аннотация применяется к простому имени типа, указанному в объявлении конструктора.
-
Во всех остальных случаях ближайший тип — это тип, написанный в исходном коде для объявляемой сущности; если этот тип является типом массива, то тип элемента считается наиболее близким к аннотации.
Например, в объявлении поля
@Foo public static String f;, тип, который наиболее близок к@Foo, — этоString. (Если тип объявления поля был написан какjava.lang.String, тоjava.lang.Stringбыл бы типом, наиболее близким к@Foo, а последующие правила запрещали бы аннотации типа применению к имени пакетаjava.) В объявлении обобщенного метода@Foo <T> int[] m() {...}, тип, записанный для объявляемой сущности —int[], поэтому@Fooприменяется к элементу типаint.Объявления локальных переменных, не использующих
var, аналогичны объявлениям формальных параметров лямбда-выражений, в том смысле, что оба позволяют аннотации объявления и аннотации типа в исходном коде, но только аннотации типа могут быть сохранены в файлеclass.
Ошибка времени компиляции, если аннотация интерфейса A является синтаксическим модификатором для:
-
объявления модуля, но A не применима к объявлениям модулей.
-
объявления пакета, но A не применима к объявлениям пакетов.
-
объявления класса или интерфейса, но A не применима к объявлениям типов или в контекстах типов; или
объявления интерфейса аннотации, но A не применима к объявлениям интерфейсов аннотаций или объявлениям типов или в контекстах типов.
-
объявления метода (включая элемент интерфейса аннотации), но A не применима к объявлениям методов или в контекстах типов.
-
объявления конструктора, но A не применима к объявлениям конструкторов или в контекстах типов.
-
объявления параметра типа обобщенного класса, интерфейса, метода или конструктора, но A не применима к объявлениям параметров типа или в контекстах типов.
-
объявления поля (или константы перечисления), но A не применима к объявлениям полей или в контекстах типов.
-
объявления формального или параметра исключения, но A не применима к объявлениям формальных и параметров исключений или в контекстах типов.
-
объявления параметра получателя, но A не применима в контекстах типов.
-
объявление локальной переменной в операторе или шаблоне, но A не применима к объявлениям локальных переменных или в контекстах типов.
-
объявления компонента записи, но A не применима к объявлениям компонентов записи, объявлениям полей, объявлениям методов, или объявлениям формальных и параметров исключений, или в контекстах типов.
Шесть из этих одиннадцати пунктов упоминают "... или в контекстах типов", потому что они характеризуют шесть синтаксических позиций, упомянутых ранее в этой секции, где аннотация могла бы быть применимой к объявлению или типу. Кроме того, два из одиннадцати пунктов — для объявлений классов и интерфейсов, и для объявлений параметров типа — упоминают "... или в контекстах типов", потому что иногда удобно применять аннотацию, чьё интерфейс мета-аннотирован @Target(ElementType.TYPE_USE) (поэтому применима в контекстах типов) к объявлению класса, интерфейса или параметра типа.
Аннотация типа допустима, если оба следующих утверждения истинны:
-
Простое имя, к которому наиболее близка аннотация, классифицируется как ИмяТипа, а не как ИмяПакет.
-
Если простое имя, к которому наиболее близка аннотация, сопровождается "
." и другим ИмяТипа — то есть, аннотация появляется как@Foo T.U— тоUобозначает внутренний классT.
Интуиция, лежащая в основе второго утверждения, заключается в том, что если Outer.this допустимо во вложенном классе, заключённом в Outer, то Outer может быть аннотировано, поскольку оно представляет тип некоторого объекта во время выполнения. С другой стороны, если Outer.this недопустимо — поскольку класс, в котором оно появляется, не имеет вложенного экземпляра Outer во время выполнения — то Outer не может быть аннотировано, поскольку оно логически просто имя, аналогично компонентам имени пакета в полном имени типа.
Например, в следующей программе невозможно написать A.this в теле B, так как у B нет лексически вложенных экземпляров. Поэтому невозможно применить @Foo к A в типе A.B, потому что A логически является просто именем, а не типом.
@Target(ElementType.TYPE_USE)
@interface Foo {}
class A {
static class B {}
}
@Foo A.B x; // Illegal
С другой стороны, в следующей программе можно написать C.this в теле D. Поэтому можно применить @Foo к C в типе C.D, так как C представляет тип некоторого объекта во время выполнения.
@Target(ElementType.TYPE_USE)
@interface Foo {}
class Test {
static class C {
class D {}
}
@Foo C.D x; // Legal
}
Если аннотация интерфейса A применяется к внешнему уровню типа в контексте типа, а A не применима в контексте типа или контексте объявления (если таковой имеется), занимающем то же синтаксическое место, это ошибка времени компиляции.
Если аннотация интерфейса A применяется к части типа (то есть не к внешнему уровню) в контексте типа, а A не применима в контексте типа, это ошибка времени компиляции.
Если аннотация интерфейса A применяется к типу (или любой части типа) в контексте типа, и A применима в контекстах типов, но аннотация недопустима, это ошибка времени компиляции.
Например, предположим интерфейс аннотации TA, который мета-аннотирован только @Target(ElementType.TYPE_USE). Термины @TA java.lang.Object и java.@TA
lang.Object недопустимы, потому что простое имя, к которому @TA ближе всего, классифицируется как имя пакета. С другой стороны, java.lang.@TA Object допустимо.
Обратите внимание, что недопустимые термины недопустимы «везде». Запрет на аннотирование имён пакетов распространяется на места, которые являются только контекстами типов, такие как class
... extends @TA java.lang.Object {...}, и на места, которые являются и контекстами объявлений, и контекстами типов, такие как @TA
java.lang.Object f;. (Не существует мест, которые являются только контекстами объявления, где имя пакета может быть аннотировано, поскольку объявления пакетов, классов, интерфейсов и параметров типа вводят только простые имена.)
Если TA дополнительно мета-аннотирован @Target(ElementType.FIELD), то термин @TA java.lang.Object допустим в местах, которые являются и контекстами объявления, и контекстами типа, таких как объявление поля @TA java.lang.Object
f;. Здесь @TA считается применимым к объявлению f (а не к типу java.lang.Object), потому что TA применимо в контексте объявления поля.
Если в контексте объявления или контексте типа появляются несколько аннотаций одного и того же интерфейса A, это ошибка времени компиляции, если A не повторяющаяся (§9.6.3), и A и содержащий интерфейс аннотации A применимы в контексте объявления или контексте типа (§9.6.4.1).
Принято, хотя и не обязательно, чтобы несколько аннотаций одного и того же интерфейса появлялись последовательно.
Если в контексте объявления или контексте типа есть несколько аннотаций повторяющегося интерфейса аннотаций A, то считается, что в контексте нет явно указанных аннотаций интерфейса A, и есть одна неявно объявленная аннотация содержащего интерфейса аннотации A.
Неявно объявленная аннотация называется контейнерной аннотацией, а несколько аннотаций интерфейса A, которые появились в контексте, называются базовыми аннотациями. Элементы (с типом массива) элемента value контейнерной аннотации — это все базовые аннотации в порядке следования слева направо, в котором они появились в контексте.
Если в контексте объявления или контексте типа есть несколько аннотаций повторяющегося интерфейса аннотаций A, и есть какие-либо аннотации содержащего интерфейса аннотации A, это ошибка времени компиляции.
Другими словами, повторение аннотаций, где аннотация того же интерфейса, что и их контейнер, также появляется, невозможно. Это запрещает запутанный код, например:
@Foo(0) @Foo(1) @FooContainer({@Foo(2)})
class A {}
Если этот код был бы допустим, то потребовалось бы несколько уровней вложения: сначала базовые аннотации интерфейса Foo содержались бы в неявно объявленной контейнерной аннотации интерфейса FooContainer, затем эта аннотация и явно объявленная аннотация интерфейса FooContainer содержались бы в ещё одной неявно объявленной аннотации. Эта сложность нежелательна с точки зрения разработчиков языка программирования Java. Другой подход, рассматривающий базовые аннотации интерфейса Foo так, как будто они произошли рядом с @Foo(2) в явной @FooContainer аннотации, нежелателен, поскольку он может изменить то, как рефлексивные программы интерпретируют @FooContainer аннотацию.
Если в контексте объявления или контексте типа есть одна аннотация повторяющегося интерфейса аннотаций A и несколько аннотаций содержащего интерфейса аннотации A, это ошибка времени компиляции.
Это правило разработано для того, чтобы позволить следующий код:
@Foo(1) @FooContainer({@Foo(2)})
class A {}
При наличии только одной базовой аннотации повторяющегося интерфейса аннотаций Foo, контейнерная аннотация не объявляется неявно, даже если FooContainer — это содержащий интерфейс аннотации Foo. Однако повторение аннотации интерфейса FooContainer, как в:
@Foo(1) @FooContainer({@Foo(2)}) @FooContainer({@Foo(3)})
class A {}
запрещено, даже если FooContainer повторяем с собственным содержащим интерфейсом аннотации. Нежелательно повторять аннотации, которые сами являются контейнерами, когда присутствует аннотация базового повторяемого интерфейса.
Функциональный интерфейс — это интерфейс, который не объявлен sealed и имеет только один abstract метод (кроме методов Object), и таким образом представляет собой контракт одной функции. Этот «один» метод может представлять собой несколько abstract методов с эквивалентными по переопределению сигнатурами, унаследованными от суперинтерфейсов; в этом случае унаследованные методы логически представляют собой один метод.
Для интерфейса I, который не объявлен sealed, пусть M будет набором abstract методов, которые являются членами I и не имеют одинаковой сигнатуры с любым public методом экземпляра класса Object (§4.3.2). Тогда I является функциональным интерфейсом, если существует метод m в M, для которого оба следующих утверждения истинны:
В дополнение к обычному процессу создания экземпляра интерфейса путем объявления и создания класса (§15.9), экземпляры функциональных интерфейсов могут быть созданы с помощью выражений ссылки на метод и лямбда-выражений (§15.13, §15.27).
Определение функционального интерфейса исключает методы в интерфейсе, которые также являются public методами в Object. Это позволяет функционально обрабатывать интерфейс, подобный java.util.Comparator<T>, который объявляет несколько abstract методов, из которых только один является действительно «новым» - int compare(T,T). Другой - boolean equals(Object) - это явное объявление abstract метода, который в противном случае был бы неявно объявлен в интерфейсе (§9.2) и автоматически реализовывался каждым классом, который implements интерфейс.
Обратите внимание, что если не-public методы Object, такие как clone(), явно объявлены в интерфейсе как public, они не автоматически реализуются каждым классом, который implements интерфейс. Реализация, унаследованная от Object, является protected, в то время как метод интерфейса является public, поэтому единственный способ реализовать интерфейс заключается в том, чтобы класс переопределил не-public Object метод с public методом.
Пример 9.8-1. Функциональные интерфейсы
Простой пример функционального интерфейса:
interface Runnable {
void run();
}
Следующий интерфейс не является функциональным, поскольку он не объявляет ничего, что не является уже членом Object:
interface NonFunc {
boolean equals(Object obj);
}
Однако его подинтерфейс может быть функциональным, объявив abstract метод, который не является членом Object:
interface Func extends NonFunc {
int compare(String o1, String o2);
}
Аналогично, всем известный интерфейс java.util.Comparator<T> является функциональным, так как он имеет один abstract не-Object метод:
interface Comparator<T> {
boolean equals(Object obj);
int compare(T o1, T o2);
}
Следующий интерфейс не является функциональным, поскольку, хотя он объявляет только один abstract метод, который не является членом Object, он объявляет два abstract метода, которые не являются public членами Object:
interface Foo {
int m();
Object clone();
}
Пример 9.8-2. Функциональные интерфейсы и стирание
В следующей иерархии интерфейсов Z является функциональным интерфейсом, так как, хотя он наследует два abstract метода, которые не являются членами Object, у них одинаковая подпись, поэтому унаследованные методы логически представляют собой один метод:
interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<String> arg); }
interface Z extends X, Y {}
Аналогично, Z является функциональным интерфейсом в следующей иерархии интерфейсов, потому что Y.m является подсигнатурой X.m и подменяемой по возвращаемому типу для X.m:
interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}
Определение функционального интерфейса учитывает тот факт, что интерфейс не может иметь два члена, которые не являются подсигнатурами друг друга, но имеют одинаковую стирание (§9.4.1.2). Таким образом, в следующих трех иерархиях интерфейсов, где Z вызывает ошибку компиляции, Z не является функциональным интерфейсом: (потому что ни один из его abstract членов не является подсигнатурой всех других abstract членов)
interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}
interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}
interface X<T> { void m(T arg); }
interface Y<T> { void m(T arg); }
interface Z<A, B> extends X<A>, Y<B> {}
Аналогично, определение «функционального интерфейса» учитывает тот факт, что интерфейс может иметь только методы с эквивалентными по переопределению сигнатурами, если один из них подменяем по возвращаемому типу для всех остальных. Таким образом, в следующей иерархии интерфейсов, где Z вызывает ошибку компиляции, Z не является функциональным интерфейсом: (потому что ни один из его abstract членов не является подменяемым по возвращаемому типу для всех других abstract членов)
interface X { long m(); }
interface Y { int m(); }
interface Z extends X, Y {}
В следующем примере объявления Foo<T,N> и Bar являются допустимыми: в каждом из них методы, называемые m, не являются подсигнатурами друг друга, но имеют разные стирания. Тем не менее, тот факт, что методы в каждом не являются подсигнатурами, означает, что Foo<T,N> и Bar не являются функциональными интерфейсами. Однако Baz является функциональным интерфейсом, потому что методы, которые он наследует от Foo<Integer,Integer>, имеют одинаковую подпись и, таким образом, логически представляют собой один метод.
interface Foo<T, N extends Number> {
void m(T arg);
void m(N arg);
}
interface Bar extends Foo<String, Integer> {}
interface Baz extends Foo<Integer, Integer> {}
Наконец, следующие примеры демонстрируют те же правила, что и выше, но с дженериками:
interface Exec { <T> T execute(Action<T> a); }
// Functional
interface X { <T> T execute(Action<T> a); }
interface Y { <S> S execute(Action<S> a); }
interface Exec extends X, Y {}
// Functional: signatures are logically "the same"
interface X { <T> T execute(Action<T> a); }
interface Y { <S,T> S execute(Action<S> a); }
interface Exec extends X, Y {}
// Error: different signatures, same erasure
Пример 9.8-3. Дженеризованные функциональные интерфейсы
Функциональные интерфейсы могут быть дженеризованными, например, java.util.function.Predicate<T>. Такой функциональный интерфейс может быть параметризован таким образом, что создает различные abstract методы - то есть несколько методов, которые нельзя законно переопределить с помощью одного объявления. Например:
interface I { Object m(Class c); }
interface J<S> { S m(Class<?> c); }
interface K<T> { T m(Class<?> c); }
interface Functional<S,T> extends I, J<S>, K<T> {}
Functional<S,T> является функциональным интерфейсом - I.m подменяем по возвращаемому типу для J.m и K.m - но тип функционального интерфейса Functional<String,Integer> явно не может быть реализован одним методом. Однако другие параметризации Functional<S,T>, которые являются типами функциональных интерфейсов, возможны.
Объявление функционального интерфейса позволяет использовать тип функционального интерфейса в программе. Существует четыре типа функциональных интерфейсных типов:
В особых случаях полезно рассматривать тип пересечения как тип функционального интерфейса. Как правило, это будет выглядеть как пересечение типа функционального интерфейса с одним или несколькими типами маркерных интерфейсов, например, Runnable &
. Такое пересечение может использоваться в приведениях типов (§15.16), которые заставляют лямбда-выражение соответствовать определенному типу. Если один из типов интерфейса в пересечении является java.io.Serializablejava.io.Serializable, срабатывает специальная поддержка выполнения для сериализации (§15.27.4).
Тип функции функционального интерфейса I — это тип метода (§8.2), который может быть использован для переопределения (§8.4.8) метода(ов) интерфейса I.
Пусть M — это множество abstract методов, определённых для I. Тип функции I состоит из следующего:
-
Параметры типа, формальные типы параметров и возвращаемый тип:
Пусть
m— метод вMс:-
сигнатурой, являющейся подсигнатурой сигнатуры каждого метода в
M; и -
возвращаемым типом R (возможно
void), где либо R совпадает с возвращаемым типом каждого метода вM, либо R — это ссылочный тип и является подтипом возвращаемого типа каждого метода вM(после адаптации для любых параметров типа (§8.4.4), если у двух методов одинаковая сигнатура).
Если такого метода не существует, то пусть
m— метод вMс:-
сигнатурой, являющейся подсигнатурой сигнатуры каждого метода в
M; и -
возвращаемым типом, таким что
mподставляется возвращаемым типом (§8.4.5) для каждого метода вM.
Параметры типа, формальные типы параметров и возвращаемый тип типа функции задаются
m. -
-
throwsописания:throwsописание типа функции получено изthrowsописаний методов вMследующим образом:-
Если тип функции является обобщённым, то
throwsописания сначала адаптируются к параметрам типа типа функции (§8.4.4).Если тип функции не обобщённый, но хотя бы один метод в
Mобобщённый, тоthrowsописания сначала стираются. -
Затем
throwsописание типа функции включает каждый тип E, который удовлетворяет следующим ограничениям:-
E указан в одном из
throwsописаний. -
Для каждого
throwsописания, E является подтипом какого-то типа, указанного в этом описании.
-
-
Когда некоторые возвращаемые типы в M являются сырыми, а другие — нет, определение типа функции пытается выбрать наиболее специфический тип, если это возможно. Например, если возвращаемые типы являются LinkedList и LinkedList<String>, то последний сразу выбирается в качестве возвращаемого типа типа функции. Когда нет наиболее специфического типа, определение компенсирует это, найдя наиболее подставляемый возвращаемый тип. Например, если есть третий возвращаемый тип List<?>, то не верно, что один из возвращаемых типов является подтипом каждого другого (так как сырой LinkedList не является подтипом List<?>); вместо этого выбирается LinkedList<String> в качестве возвращаемого типа типа функции, потому что он подставляется возвращаемым типом для обоих LinkedList и List<?>.
Цель, определяющая определение типов исключений, выбрасываемых типом функции, заключается в обеспечении инварианта, что метод с полученным throws описанием может переопределять каждый abstract метод функционального интерфейса. Согласно §8.4.6, это означает, что тип функции не может выбрасывать "больше" исключений, чем любой один метод в множестве M, поэтому мы ищем как можно больше типов исключений, которые "покрываются" описанием throws каждого метода.
Тип функции типа функционального интерфейса задаётся следующим образом:
-
Тип функции типа необобщённого функционального интерфейса I — это просто тип функции функционального интерфейса I, как определено выше.
-
Тип функции параметризованного типа функционального интерфейса I
<A1...An>, где A1...An — типы, а соответствующие параметры типа I — P1...Pn, выводится путём применения подстановки[P1:=A1, ..., Pn:=An]к типу функции обобщённого функционального интерфейса I<P1...Pn>. -
Тип функции параметризованного типа функционального интерфейса I
<A1...An>, где один или несколько из A1...An являются джойлвайдами, — это тип функции не-джойлвайд параметризации I, I<T1...Tn>. Не-джойлвайд параметризация определяется следующим образом.Пусть P1...Pn — параметры типа I с соответствующими ограничениями B1...Bn. Для всех i (1 ≤ i ≤ n), Ti определяется в зависимости от формы Ai:
-
Если Ai — тип, то Ti = Ai.
-
Если Ai — джойлвайд, и соответствующее ограничение параметра типа Bi упоминает один из P1...Pn, то Ti не определено, и типа функции нет.
-
В противном случае:
-
Если Ai — неограниченный джойлвайд
?, то Ti = Bi. -
Если Ai — верхне-ограниченный джойлвайд
?extendsUi, то Ti = glb(Ui, Bi) (§5.1.10). -
Если Ai — нижне-ограниченный джойлвайд
?superLi, то Ti =Li.
-
-
-
Тип функции сырого типа обобщённого функционального интерфейса I
<...>— это стирание типа функции обобщённого функционального интерфейса I<...>. -
Тип функции типа пересечения, который порождает воображаемый функциональный интерфейс, — это тип функции воображаемого функционального интерфейса.
Пример 9.9-1. Типы функций
Даны следующие интерфейсы:
interface X { void m() throws IOException; }
interface Y { void m() throws EOFException; }
interface Z { void m() throws ClassNotFoundException; }
тип функции:
interface XY extends X, Y {}
равен:
()->void throws EOFException
в то время как тип функции:
interface XYZ extends X, Y, Z {}
равен:
()->void (throws nothing)
Даны следующие интерфейсы:
interface A {
List<String> foo(List<String> arg)
throws IOException, SQLTransientException;
}
interface B {
List foo(List<String> arg)
throws EOFException, SQLException, TimeoutException;
}
interface C {
List foo(List arg) throws Exception;
}
тип функции:
interface D extends A, B {}
равен:
(List<String>)->List<String>
throws EOFException, SQLTransientException
в то время как тип функции:
interface E extends A, B, C {}
равен:
(List)->List throws EOFException, SQLTransientException
Тип функции функционального интерфейса определяется не однозначно: хотя сигнатуры в M "одинаковы", они могут быть синтаксически различными (HashMap.Entry и Map.Entry, например); тип возвращаемого значения может быть подтипом любого другого типа возвращаемого значения, но могут быть и другие типы возвращаемых значений, которые также являются подтипами (List<?> и List<? extends
Object>, например); и порядок типов, выбрасываемых в исключениях, не определён. Эти различия тонкие, но иногда они могут быть важными. Однако, типы функций не используются в языке программирования Java таким образом, чтобы неопределённость имела значение. Обратите внимание, что тип возвращаемого значения и throws клауза "самого специфичного метода" также определяются не однозначно, когда есть несколько abstract методов (§15.12.2.5).
Когда обобщённый функциональный интерфейс параметризуется маркерами подстановок, существует много различных экземпляров, которые могли бы удовлетворить маркер подстановки и произвести разные типы функций. Например, каждый из Predicate<Integer> (тип функции Integer ), -> booleanPredicate<Number> (тип функции Number ) и -> booleanPredicate<Object> (тип функции Object ) является -> booleanPredicate<? super Integer>. Иногда из контекста, например, типов параметров лямбда-выражения, можно узнать, какой тип функции подразумевается (§15.27.3). В других случаях необходимо выбрать один; в этих случаях используются ограничения. (Эта простая стратегия не может гарантировать, что полученный тип будет удовлетворять определённым сложным ограничениям, поэтому не все сложные случаи поддерживаются.)
Пример 9.9-2. Обобщённые типы функций
Тип функции может быть обобщённым, поскольку abstract метод функционального интерфейса может быть обобщённым. Например, в следующей иерархии интерфейсов:
interface G1 {
<E extends Exception> Object m() throws E;
}
interface G2 {
<F extends Exception> String m() throws Exception;
}
interface G extends G1, G2 {}
тип функции G равен:
<F extends Exception> ()->String throws F
Обобщённый тип функции для функционального интерфейса может быть реализован выражением ссылки на метод (§15.13), но не лямбда-выражением (§15.27), поскольку нет синтаксиса для обобщённых лямбда-выражений.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.