Главная » Java » Расширение интерфейсов Java

0

Интерфейсы допускают расширение; в этом случае в объявлении производного интерфейса используется служебное слово extends. Интерфейсы, в отличие от классов, способны расширять более одного интерфейса:

public interface SerializableRunnable

extends java.io.serializable, Runnable

{

            // …

}

 

Интерфейс SerializableRunnable одновременно наследует интерфейсы java.io.Serializable и Runnable. Это означает, что все методы и константы, определенные в каждом унаследованном интерфейсе, становятся, наряду с собственными методами и константами, частью контракта нового интерфейса SerializableRunnablе. Наследуемые интерфейсы называют базовыми (superinterfaces) по отношению к новому интерфейсу, который, в свою очередь, является производным (subinterface), или расширенным, интерфейсом относительно базовых.

Поскольку интерфейсы поддерживают модель множественного наследования, граф, представляющий соответствующую иерархию типов, способен содержать целый ряд циклов или путей, имеющих отношение к одному и тому же базовому интерфейсу. Это значит, что доступ к константам и методам базового интерфейса Может быть получен несколькими способами. Однако, поскольку интерфейсы не содержат блоков реализации и полей экземпляров, модель множественного наследования в этом случае не таит в себе каких бы то ни было "подводных камней".

Наследование и сокрытие констант

Производный интерфейс наследует все константы, объявленные в базовых интерфейсах. Если в производном интерфейсе объявлена константа с тем же именем, что и унаследованная, то, независимо от их типов, новая константа скрывает "старую". Те же правила справедливы и в отношении унаследованных полей классов (см. раздел 3.3.2 на странице 96). Ссылка на константу посредством простого имени в контексте производного интерфейса или Класса, который реализует этот интерфейс, означает обращение к константе, принадлежащей про из водному (а не базовому) интерфейсу. Константа, унаследованная от базового интерфейса, все еще доступна, если при ссылке на нее указывать полное имя, Т.е. название интерфейса, сопровождаемое оператором точки и идентификатором самой константы. Точно так же обычно выполняется обращение к статическим членам классов.

interface х { int val = 1;

}

interface У extends х { int val = 2;

int sum = val + Х.val;

}

Интерфейс У содержит объявления двух констант – val и sum. Чтобы обратиться к скрытой константе vа1, унаследованной от интерфейса Х, необходимо указать ее полное имя – Х. val. Во внешнем коде для ссылки на константы интерфейса У можно использовать обычную форму, принятую при обращении к статическим членам класса, – У. val и У. sum. Разумеется, посредством выражения Х. val  вы получите доступ и к константе val интерфейса Х.

Указанные правила, повторим, применяются и для доступа к унаследованным статическим полям классов

Если интерфейс У реализуется каким-либо классом, константы интерфейса У с точки зрения этого класса будут выглядеть как члены класса. Например, в контексте класса, объявление которого выглядит как class Z implements У {}, вполне допустимо следующее выражение: System.out.println("z.val=" + Z.val + "Z.sum=" + Z. sum);

Но при этом, правда, отсутствует возможность обращения посредством Z к Х.val. Однако, если ссылка на объект класса Z существует, адресовать Х.val Удастся с помощью оператора преобразования типов.

Результат работы кода, как и следовало ожидать, будет выглядеть так

z.val=2, ((Y)z).val=2, ((x)z).val=1

Мы вновь наблюдаем ту же картину, что и при использовании статических полей в расширенных классах. Таким образом, не имеет значения, откуда унаследовано статическое поле – из базового класса или базового интерфейса

Хотя все это безусловно полезно с точки зрения цельности и последовательности языка, подобные вещи имеют небольшое практическое применение – веские причины для сокрытия полей отсутствуют, а доступ к статическим полям класса или интерфейса всегда должен осуществляться посредством задания имени Типа, в котором они объявлены.

Если интерфейс наследует несколько констант с одним и тем же именем, Использование этого имени без дополнительных разъяснений чревато недоразумениями и приводит К ошибке периода компиляции. Продолжим пример, касающийся интерфейсов Х и У, и предположим, что объявлены еще два интерфейса:

interface С {

String val = "интерфейс С”; }

interface D extends х, с {}

Что теперь может означать выражение D.val – обращение к целочисленной константе val или строковому литералу val? В подобных случаях мы обязаны явно оговаривать свои намерения – достаточно написать х. val или С. val.

Класс, который реализует более одного интерфейса либо расширяет базовый класс и при этом реализует интерфейсы, приводит нас к тем же трудностям, связанным с сокрытием данных и неоднозначностью имен, что и интерфейс, наследующий несколько других интерфейсов, – наличие в классе собственных статических полей обусловливает сокрытие одноименных унаследованных полей базовых классов или интерфейсов, и обычные ссылки на такие поля будут выглядеть двусмысленно.

Наследование, переопределение и перегрузка методов

Производный интерфейс наследует все методы базовых интерфейсов. Если метод, объявленный в производном интерфейсе, обладает теми же сигнатурой (наименованием и списком параметров) и типом возвращаемого значения, что и унаследованный метод, новое объявление переопределяет любое и все аналогичные объявления унаследованных методов. Переопределение в интерфейсах, в отличие от переопределения в классах, не несет какой-либо семантической нагрузки – интерфейс в результате наследования способен содержать несколько объявлений одного и того же метода, но в любом классе, реализующем интерфейс, может присутствовать только одна реализация этого метода.

Если интерфейс наследует более одного метода с одной и той же сигнатурой или класс реализует несколько интерфейсов, содержащих метод с одинаковой сигнатурой, все равно можно говорить о том, что существует только один такой метод – а именно тот, конкретный вариант кода которого в конечном итоге представлен в классе, реализующем интерфейс. В этом случае поводов для двоякого толкования не существует.

Подлинная проблема заключается в том, способна ли единственная реализация метода удовлетворять всем контрактам, подразумеваемым унаследованными интерфейсами, в которых содержатся объявления этого метода. При определенных обстоятельствах подобная задача становится неразрешимой. Вот пример:

interface CardDealer {

void draw(); // снять верхнюю карту колоды void deal(); // Раздать карты

void shufflе () ;

interface GraphicalComponent {

            void draw();                       // отобразить на устройстве по умолчанию

void draw(Device d);         // отобразить на устройстве ‘d’

 void rotate(int degrees);

void fill(Color с);

interface GraphicalCardDealer

extends CardDealer, GraphicalComponent {}

В данной ситуации трудно представить реализацию метода draw, которая независимо удовлетворяла бы двум совершенно различным контрактам. Если вы Все-таки попытаетесь это сделать, то добьетесь явно не того, чего хотели, – перерисовки экрана при каждой операции снятия верхней карты колоды.

Как и при переопределении методов в процессе расширения класса, методу, переопределенному при наследовании интерфейса, не позволяется декларировать больше объявленных исключений, нежели предусмотрено в исходном объявлении соответствующего метода. Если наследуются (без переопределения) два или более метода и их объявления различаются только предложением throws, при реализации этого метода должны учитываться все перечисленные в предложениях throws объявленные исключения. И вновь главная проблема состоит в том, удастся ли в рамках одной реализации удовлетворить требованиям всех контрактов. В главе 8 мы еще раз обратим ваше внимание на вопросы, связанные с переопределением методов и объявлением исключений.

Если в составе интерфейса объявлен метод с тем же именем, но иным списком параметров, нежели в унаследованном интерфейсе, имеет место перегрузка метода. Класс, реализующий интерфейс, должен будет предоставить конкретные варианты кода для каждой из перегруженных форм метода.

Если объявленный в интерфейсе метод отличается от унаследованного только типом возвращаемого значения, при компиляции будет выдано сообщение об ошибке.

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии