Содержание
| Предыдущая | СледующаяГЛАВА 9
Объявление интерфейса вводит новый ссылочный тип, членами которого являются константы и абстрактные методы. Этот тип не может быть реализован, но его могут реализовывать несвязанные классы, обеспечивая реализацию его абстрактных методов.
Программы на Яве могут использовать интерфейсы для того, чтобы упускать для связанных классов разделение общего абстрактного суперкласса или для добавления методов в Object.
Интерфейс может быть объявлен, для того чтобы быть прямым расширением одного или более интерфейсов, предполагая, что он неявно определяет все абстрактные методы и константы расширяющих его интерфейсов, кроме каких-нибудь констант, которые могут быть скрыты.
Класс может быть объявлен для того, чтобы непосредственно реализовать один или более интерфейсов, в том смысле, что любой экземпляр класса реализует все абстрактные методы заданные интерфейсом или интерфейсами. Класс обязательно реализует все интерфейсы, что и его непосредственные суперклассы и непосредственные суперинтерфейсы. Это (множественное) наследование интерфейса позволяет объектам поддерживать (множественное) общее поведение без разделения реализации.
Переменная, чей объявленный тип - это интерфейсный тип, может иметь в качестве значения ссылку на какой-нибудь объект, который является экземпляром класса, объявленного для реализации указанного интерфейса. Этого не достаточно для того, чтобы класс производил реализацию всех абстрактных методов интерфейса; класс или один из его суперклассов должен фактически быть объявлен, чтобы реализовать интерфейс, или иначе класс не учитывается при реализации интерфейса.
Объявление интерфейса определяет новый ссылочный тип:
InterfaceDeclaration: InterfaceModifiersoptinterface
Identifier ExtendsInterfacesoptInterfaceBody
Ошибка времени компиляции происходит, если Identifier, являющийся именем интерфейса появляется как имя какого-нибудь другого класса или интерфейса в том же самом пакете. Также ошибка времени компиляции происходит, если Identifier, являющийся именем интерфейса появляется как имя, под которым класс или интерфейс должен быть известен через объявление одиночного импорта типа (§7.5.1) в модуле компиляции содержащем объявление интерфейса. В примере: class Point { int x, y; } interface Point { void move(int dx, int dy); }
ошибка времени компиляции происходит, потому что class и interface в одном и том же пакете не могут иметь одинаковые имена.
9.1.1 Область действия имени интерфейсного типаIdentifier определяет имя интерфейса областью действия которого является весь пакет, в котором он объявлен. Это правило схоже с правилом для имен классового типа; смотрите §8.1.1 пример, содержащий классы.
Объявлению интерфейса может предшествовать объявление модификаторов:
InterfaceModifiers:InterfaceModifier
InterfaceModifiers
InterfaceModifier InterfaceModifier: один из
public abstract
Модификатор доступа public обсуждался в §6.6. Если один и тот же модификатор появляется более одного раза в объявлении интерфейса, то происходит ошибка времени компиляции.
Каждый интерфейс по умолчанию является abstract (абстрактным). Этот модификатор устарел и не используется в новых программах на Яве.
Если предусмотрено предложение extends, тогда объявляемый интерфейс расширяет каждый из перечисленных интерфейсов и поэтому наследует методы и константы каждого из названных интерфейсов. Эти указанные интерфейсы есть прямые суперинтерфейсы объявляемого интерфейса. Любой класс, который реализует объявленный интерфейс, также обеспечивает реализацию всех интерфейсов, которые расширяют интерфейс и которые доступны для класса.
ExtendsInterfaces:extends
InterfaceType ExtendsInterfaces,
InterfaceType
Для большей ясности, следующее повторяется из §4.3:
InterfaceType: TypeName
Каждый InterfaceType в предложении extends из описания интерфейса должен именовать доступный интерфейсный тип; иначе происходит ошибка времени компиляции.
Ошибка времени компиляции происходит, если имеется рекурсия такая что интерфейс непосредственно или косвенно расширяет себя.
Нет никакого аналога классу Object для интерфейсов; то есть, в то время как каждый класс - расширение класса Object, нет никакого отдельного интерфейса в котором все интерфейсы являются расширениями.
Отношение суперинтерфейса - переходное смыкание отношения прямого суперинтерфейса. Интерфейс K - суперинтерфейс интерфейса I, если следующее является верным:
Интерфейс I, как говорят, является подинтерфейсом интерфейса K, когда K - суперинтерфейс I.
Тело интерфейса может объявлять члены интерфейса:
InterfaceBody:{
InterfaceMemberDeclarationsopt}
InterfaceMemberDeclarations: InterfaceMemberDeclaration InterfaceMemberDeclarationsInterfaceMemberDeclaration InterfaceMemberDeclaration: ConstantDeclaration AbstractMethodDeclaration
Область действия члена, объявленного в типе интерфейса - все тело объявленного интерфейса.
Все члены интерфейса по умолчанию public. Они доступны вне пакета, где интерфейс объявлен, если интерфейс также объявлен как public и пакет, содержащий интерфейс - доступен, как описывается в §7.1.
Члены интерфейса - это члены, которые унаследованы от прямого суперинтерфейса и члены, которые объявлены в интерфейсе.
Интерфейс наследует из интерфейсов расширения, все члены этих интерфейсов, кроме полей, которые скрываются и методов которые игнорируются.
9.3 Объявление (константного) поля
ConstantDeclaration: ConstantModifiersType
VariableDeclarator ConstantModifiers: one of
public static final
Каждое объявление поля в теле интерфейса
поумолчанию public
, static
и final
.
Это разрешено,но рекомендуется как соответствие
со стилем,резервно определять любой или все
этимодификаторы для таких полей.
Объявление константы в интерфейсе не должно включать никакой из модификаторов -synchronized, transient, или volatile, иначе происходит ошибка времени компиляции .
Это возможно для интерфейса, чтобы унаследовать больше чем одно поле с одним и тем же именем (§8.3.3.3). Такая ситуация сама по себе не вызывает ошибки времени компиляции. Однако, любая попытка в пределах тела интерфейса обратиться к некоторому полю при помощи простого имени окончится ошибкой времени компиляции, потому что такая ссылка неоднозначна.
Могло бы существовать несколько путей, которыми одно и то же объявление поля могло бы наследоваться от интерфейса. В такой ситуации, поле рассматривается как унаследованное только один раз, и это может быть обращено с помощью простого имени без двусмысленности.
Каждое поле в теле интерфейса должно иметь выражение инициализации, которое не должно быть константой. Инициализатор -переменная определена и присваивание, выполняется только один раз, когда интерфейс - инициализирован (§12.4).
Ошибка времени компиляции происходит, если инициализация выражения поля интерфейса содержит ссылку на простое имя того же поля или другого поля, текстовое объявление которого в том же самом интерфейсе происходит позже. Таким образом:
interface Test { float f = j; int j = 1; int k = k+1; } }
происходят две ошибки времени компиляции, потому что j упомянута в инициализации f прежде j объявлен и потому что инициализация k обращается к k.
(Одна тонкость состоит в том, что, во время выполнения, fields, которые инициализируются постоянными значениями во время компиляции - инициализируются первыми. Это также применяется к полям static final в классах (§8.3.2.1). Это означает, в частности, что эти поля никогда не будут иметь их начальные значения по умолчанию (§4.5.4). Для более углубленного изучения см. § 12.4.2 и § 13.4.8.)
Если ключевое слово this (§ 15.7.2) или ключевое слово super (§ 15.10.2, § 15.11) происходит в инициализации выражения для поля интерфейса, тогда происходит ошибка времени компиляции.
Следующий пример иллюстрирует некоторые (возможно тонкие) пункты относительно объявления поля.
Если два поля с одинаковым именем унаследованы интерфейсом, например, два из его прямых суперинтерфейсов объявлены полями с тем же именем, тогда результатом является один неоднозначный член. Любое использование этого неоднозначного члена окончится ошибкой времени компиляции. Таким образом в примере:
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 должен был присвоить YELLOW значение 3 - скорее чем значение 8, ссылка на поле YELLOW в пределах интерфейса LotsOfColors все еще рассматривается неоднозначно.
Если одно поле унаследовано много раз от одного и того же интерфейса, например, интерфейс и один из прямых суперинтерфейсов этого интерфейса расширяют интерфейс, который объявляет поле, то результатом является только один член. Эта ситуация сама по себе не вызывает ошибку времени-компиляции.
В примере в предыдущей части, поля RED, GREEN, и BLUE наследовались интерфейсом LotsOfColors больше чем одним способом, через интерфейс RainbowColors и также через интерфейс PrintColors, но ссылка на поле RED в интерфейсе LotsOfColors не рассматривается неоднозначно потому что вовлечено только одно фактическое объявление поля RED.
AbstractMethodDeclaration: AbstractMethodModifiersoptResultType
MethodDeclarator
Throwsopt
;
AbstractMethodModifiers: AbstractMethodModifier AbstractMethodModifiersAbstractMethodModifier AbstractMethodModifier: one of
public abstract
Модификатор доступа public рассмотрен в §6.6. Ошибка времени компиляции происходит, если тот же самый модификатор появляется больше, чем один раз в объявлении абстрактного метода.
Каждое объявление метода в теле интерфейса по умолчанию abstract, так его тело всегда представляется точкой с запятой, а не блоком. Для совместимости с более старыми версиями языка Ява, это разрешено, но рекомендуется, как соответствие со стилем, резервно определять модификатор abstract для методов, объявленных в интерфейсах.
Каждое объявление метода в теле интерфейса по умолчанию - public. Это разрешено, но рекомендуется как соответствие со стилем, резервно определять модификатор public для методов интерфейса.
Заметьте, что метод, объявленный в интерфейсе не должен объявляться как static, или произойдет ошибка времени компиляции, потому что в языке Ява методы static не могут быть abstract.
Заметьте, что метод, объявленный в интерфейсе не должен быть объявлен как native или synchronized, или произойдет ошибка времени компиляции, потому что ключевые слова описывают скорее свойства реализации чем свойства интерфейса. Однако, метод, объявленный в интерфейсе может быть реализован методом который объявляется как native или synchronized в классе, который реализует интерфейс.
Заметьте, что метод, объявленный в интерфейсе не должен быть объявлен как final или произойдет ошибка времени компиляции. Однако, метод, объявленный в интерфейсе может быть реализован методом, который объявлен как final в классе, который реализует интерфейс.
Если интерфейс объявляет метод, то объявление этого метода как говорят замещает все методы с той же самой сигнатурой в суперинтерфейсах интерфейса, который был бы иначе доступны коду в этом интерфейсе.
Если метод объявлен в интерфейсе замещает объявление метода в другом интерфейсе, произойдет ошибка времени компиляции если методы имеют различные типы возврата или если один имеет тип возврата, а другой - void. Кроме того, объявление метода не должно иметь предложения throws которое конфликтует с любыми из тех методов, которые замещаются; иначе, произойдет ошибка времени компиляции.
Методы замещаются в соответствии с сигнатурами. Если, например, интерфейс объявляет два метода public с одним и тем же именем, и подинтерфейс замещает один из них, то подинтерфейс все еще наследует другой метод.
Интерфейс наследует от его непосредственных суперинтерфейсов все методы суперинтерфейсов, которые - не замещаются объявлением в интерфейсе.
Это возможно для интерфейса, чтобы унаследовать больше чем один метод с одной и той же сигнатурой (§ 8.4.2). Такая ситуация сама по себе не вызывает ошибки времени компиляции. Интерфейс унаследует все методы. Однако, произойдет ошибка времени компиляции если, для любых двух таких унаследованных методов, или они имеют различные типы возврата, или один имеет тип возврата, а другой - void. (В этом случае предложение throws не генерирует ошибки.)
Существует несколько путей, которыми одно и тоже объявление метода наследуется от интерфейса. Это не вызывает никаких трудностей и никогда не приводит к ошибке при компиляции.
Если два метода интерфейса (или оба объявлены в одном и том же интерфейсе, или оба наследованы интерфейсом, или один объявлен и один унаследован) имеют одно и то же имя, но различные сигнатуры, то имя метода, как говорят, является игнорированным. Это не вызывает никаких трудностей и никогда не приводит к ошибке времени компиляции. Типы возвращаемых значений и предложения trows двух методов с одинаковым именем и разными сигнатурами не имеют друг к другу никакого отношения.
Следующие примеры иллюстрируют некоторый (возможно тонкие) пункты относительно объявлений абстрактных методов.
Методы, объявленные в интерфейсах - abstract и таким образом не содержат реализацию. Относительно того, что может быть выполнено замещением объявления метода, подтверждает сигнатура метода, что должны быть ограничены исключения, которые могли бы быть брошен реализацией метода. Кроме задания сигнатуры в объявлении замещающего метода указываются исключения, которые могут быть сгенерированы реализацией метода. Здесь вариант примера, показанного в §8.4.3.1:
class BufferEmpty extends Exception { BufferEmpty() { super(); } BufferEmpty(String s) { super(s); } } class BufferError extends Exception { BufferError() { super(); } BufferError(String s) { super(s); } } public interface Buffer { char get() throws BufferEmpty, BufferError; } public interface InfiniteBuffer extends Buffer { char get() throws BufferError; // override }
В коде примера:
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 с тремя различными сигнатурами, две из них объявлены и одна унаследована. Любой класс, который реализует интерфейс RealPointInterface, должен обеспечить реализацию всех трех сигнатур метода.
Содержание
| Предыдущая | Следующая