К. Арнольд, Д. Гослинг - Язык программирования Java (1160779), страница 53
Текст из файла (страница 53)
Первый из них добавляет новый атрибут в объект Attributed; второй проверяет, включался лиранее в объект атрибут с указанным именем; третий удаляет атрибут из объекта; четвертый возвращает список атрибутов, закрепленных заобъектом. В последнем из них используется интерфейс Enumeration, определенный для классов-коллекций Java. java.util.enumeration подробнорассматривается в главе 12.Все методы, входящие в интерфейс, неявно объявляются абстрактными; так как интерфейс не может содержать собственной реализацииобъявленных в нем методов. Поэтому нет необходимости объявлять их с ключевым словом abstract.
Каждый класс, реализующий интерфейс,должен реализовать все его методы; если же в классе реализуется только некоторая часть методов интерфейса, такой класс (в обязательномпорядке) объявляется abstract.Методы интерфейса всегда являются открытыми. Они не могут быть статическими, поскольку статические методы всегда относятся кконкретному классу и никогда не бывают абстрактными, а интерфейс может включать только абстрактные методъ.С другой стороны, поля интерфейса всегда объявляются static и final. Они представляют собой константы, используемые при вызове методов.Например, интерфейс, в контракте которого предусмотрено несколько уровней точности, может выглядеть следующим образом:interface Verbose {int SILENT = 0;int TERSE= 1;int NORMAL = 2;int VERBOSE = 3;void setVerbosity(int level);int getVerbosity();}Константы SILENT, TERSE, NORMAL и VERBOSE передаются методу set Verbosity; таким образом можно присвоить имена постоянным величинам,имеющим конкретное значение. Они должны быть константами, а все поля интерфейса неявно объявляются static и final.4.2.
Одиночное и множественное наследованиеВ языке Java новый класс может расширять всего один суперкласс — такая модель носит название одиночного наследования. Расширение классаозначает, что новый класс наследует от своего суперкласса не только контракт, но и реализацию. В некоторых объектно-ориентированныхязыках используется множественное наследование, при котором новый класс может иметь два и более суперклассов.Множественное наследование оказывается полезным в тех случаях, когда требуется наделить класс новыми возможностями и при этомсохранить большую часть (или все) старых свойств.
Однако при наличии нескольких суперклассов возникают проблемы, связанные сдвойственным наследованием. Например, рассмотрим следующую иерархию типов:Обычно такая ситуация называется “ромбовидным наследованием”, и в ней нет ничего плохого — подобная структура встречается довольночасто. Проблема заключается в наследовании реализации. Если класс W содержит открытое поле goggin и у вас имеется ссылка на объект типа Zс именем zref, то чему будет соответствовать ссылка zref.goggin? Будет ли она представлять собой копию goggin из класса X, или из класса Y, илиже X и Y будут использовать одну копию goggin, поскольку в действительности W входит в Z всего один раз, хотя Z одновременно является и X, иY?Чтобы избежать подобных проблем, в Java используется объектно-ориентированная модель с одиночным наследованием.Одиночное наследование способствует правильному проектированию. Проблемы множественного наследования возникают из расширенияклассов при их реализации.
Поэтому Java предоставляет возможность наследования контракта без связанной с ним реализации. Для этоговместо типа class используется тип interface.Таким образом, интерфейсы входят в иерархию классов и наделяют Java возможностями множественного наследования.Классы, расширяемые данным классом, и реализованные им интерфейсы совместно называются его супертипами; с точки зрения супертипов,новый класс является подтипом.
В понятие “полного типа” нового класса входят все его супертипы, поэтому ссылка на объект класса можетиспользоваться полиморфно — то есть всюду, где должна находиться ссылка на объект любого из супертипов (класса или интерфейса).Определения интерфейсов создают имена типов, подобно тому как это происходит с именами классов; вы можете использовать имя интерфейсав качестве имени переменной и присвоить ей любой объект, реализующий данный интерфейс.4.3.
Расширение интерфейсовИнтерфейсы также могут расширяться с помощью ключевого слова extended. В отличие от классов, допускается расширение интерфейсом сразунескольких других интерфейсов:interface Shimmer extends FloorWax, DessertTopping {double amazingPrice();}Тип Shimmer расширяет FloorWax и DessertTopping; это значит, что все методы и константы, определенные в FloorWax и DessertTopping, являютсясоставной частью его контракта, и к ним еще добавляется метод amazingPrice.Если вы хотите, чтобы ваш класс реализовывал интерфейс и при этом расширял другой класс, то вам необходимо применить множественноенаследование. Другими словами, у вас появляется класс, объекты которого могут использоваться там, где допускаются типы суперкласса исуперинтерфейса.
Давайте рассмотрим следующее объявление:interface W { }interface X extends W { }class Y implements W { }class Z extends Y implements X { }Ситуация отчасти напоминает знакомое нам “ромбовидное наследование”, но на этот раз нет никаких сомнений по поводу того, какие поля, Xили Y, должны использоваться; у X нет никаких полей, поскольку это интерфейс, так что остается только Y. Диаграмма наследования будетвыглядеть следующим образом:W, X и Y могли бы быть интерфейсами, а Z — классом.
Вот как бы это выглядело:interface W { }interface X extends W { }interface Y extends W { }class Z implements X, Y { }Z оказывается единственным классом, входящим в данную иерархию.У интерфейсов, в отличие от классов, нет единого корневого интерфейса (аналогичного классу Object), лежащего в основе всей иерархии.Несмотря на это, вы можете передать выражение любого из интерфейсных типов методу, получающему параметр типа Object, потому что объектдолжен принадлежать к какому-то классу, а все классы являются подклассами Object. Скажем, для приведенного выше примера допускаетсяследующее присваивание переменной obj:protected void twiddle(W wRef) {Object obj = wRef;// ...}4.3.1.
Конфликты именДва последних примера наглядно демонстрируют, что класс или интерфейс может быть подтипом более чем одного интерфейса. Возникаетвопрос: что произойдет, если в родительских интерфейсах присутствуют методы с одинаковыми именами? Если интерфейсы X и Y содержатодноименные методы с разным количеством или типом параметров, то ответ прост: интерфейс Z будет содержать два перегруженных метода содинаковыми именами, но разными сигнатурами. Если же сигнатуры в точности совпадают, то ответ также прост: Z может содержать лишь одинметод с данной сигнатурой. Если методы отличаются лишь типом возвращаемого значения, вы не можете реализовать оба интерфейса.Если два метода отличаются только типом возбуждаемых исключений, метод вашего класса обязан соответствовать обоим объявлениям содинаковыми сигнатурами (количеством и типом параметров), но может возбуждать свои исключения.
Однако методы в пределах класса недолжны отличаться только составом возбуждаемых исключений; следовательно, должна присутствовать всего одна реализация,удовлетворяющая обоим связкам throws. Поясним сказанное на примере:interface X {void setup() throws SomeExeption;}interface Y {void setup();}class Z implements X, Y {public void setup() {// ...}}В этом случае класс Z может содержать единую реализацию, которая соответствует X.setup и Y.setup.
Метод может возбуждать меньшеисключений, чем объявлено в его суперклассе, поэтому при объявлении Z.setup необязательно указывать, что в методе возбуждаетсяисключение типа Some Exception. X.setup только разрешает использовать данное исключение. Разумеется, все это имеет смысл лишь в томслучае, если одна реализация может удовлетворить контрактам обоих методов, — если два метода подразумевают нечто разное, то вам, по всейвидимости, не удастся написать единую реализацию для них.Если списки исключений расходятся настолько, что вам не удается объявить методы так, чтобы они удовлетворяли сигнатурам обоихинтерфейсов, то вы не сможете ни расширить эти интерфейсы, ни построить реализующий их класс.С константами интерфейсов дело обстоит проще. Если в двух интерфейсах имеются константы с одинаковыми именами, то вы всегда сможетеобъединить их в дереве наследования, если воспользуетесь уточненными (qualified) именами констант.