Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие Java .pdf
Скачиваний:
118
Добавлен:
16.03.2015
Размер:
1.32 Mб
Скачать

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

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

Производный интерфейс наследует все константы, объявленные в базовых интерфейсах. Если в производном интерфейсе объявлена константа с тем же именем, что и унаследованная, то, независимо от их типов, новая константа «скрывает» старую (те же правила справедливы и в отношении унаследованных полей классов).

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

Пример 75. Сокрытие констант при расширении интерфейсов interface X {

int VAL = 1;

}

interface Y extends X { int VAL = 2;

int SUM = VAL + X.VAL;

}

Интерфейс Y содержит объявления двух констант – VAL и SUM. Чтобы обратиться к скрытой константе VAL, унаследованной из

168

интерфейса X, необходимо указать её полное имя – X.VAL. Во внешнем коде для ссылки на константы интерфейса Y можно использовать обычную форму, принятую при обращении к статическим членам класса – Y.VAL и Y.SUM. Разумеется, посредством выражения X.VAL вы получите доступикконстантеVAL интерфейса X.

Если интерфейс Y реализуется каким-либо классом, константы интерфейса Y с точки зрения этого класса будут выглядеть как члены класса. Например, в контексте класса, объявление которого выглядит как class Z implements Y {}, вполне допустимо следующее выражение:

System.out.println("Z.VAL=" + Z.VAL + "Z.SUM=" + Z.SUM);

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

Z z = new Z();

System.out.println("z.VAL=" + z.VAL + ", ((Y)z).VAL=" + ((Y)z).VAL + ", ((X)z).VAL=" + ((X)z).VAL);

Результат работы кода будет выглядеть так:

z.VAL=2, ((Y)z).VAL=2, ((X)z).VAL=1

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

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

169

Пример 76. Проблема конфликта констант при множественном наследовании interface C {

String VAL = "Интерфейс C";

}

interface D extends X, C {}

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

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

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

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

иунаследованный метод, новое объявление переопределяет любое

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

Если интерфейс наследует более одного метода с одной и той же сигнатурой, или класс реализует несколько интерфейсов, содержа-

170

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

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

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

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

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

5.9 Пустые интерфейсы

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

интерфейсами-маркерами (marker interface) – может служить

171

Соседние файлы в предмете Программирование на Java