Кен Арнольд Джеймс Гослинг
Вид материала | Документы |
Содержание4.3. Расширение интерфейсов 4.3.1. Конфликты имен 4.4. Реализация интерфейсов |
- Джеймс трефил, 41001.36kb.
- Джеймс А. Дискретная математика и комбинаторика [Текст] / Джеймс А. Андерсон, 42.79kb.
- Человеческая способность эти ценности производить и использовать; является важнейшей, 110.76kb.
- Джеймс блиш города в полете 1-4 триумф времени вернись домой, землянин жизнь ради звезд, 10495.38kb.
- Джеймс Н. Фрей. Как написать гениальный роман, 2872.12kb.
- Дп «авто интернешнл» Київ, вул. Урицького, 1а Тел. (044) 20-60-333 Факс. (044) 20-60-343, 82.44kb.
- Тема Кол-во страниц, 26.85kb.
- Тема Кол-во страниц, 56.3kb.
- Тема Кол-во страниц, 20.7kb.
- Арнольд И. В. Стилистика современного английского языка, 20.42kb.
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) именами констант. Пусть интерфейсы PokerDeck и TarotDeck включают константы DECK_SIZE с различными значениями, а интерфейс или класс MultiDeck может реализовать оба этих интерфейса. Однако внутри Multi Deck и его подтипов вы должны пользоваться уточненными именами Poker Deck.DECK_SIZE и TarotDeck.DECK_SIZE, поскольку простое DECK_SIZE было бы двусмысленным.
4.4. Реализация интерфейсов
Интерфейс описывает контракт в абстрактной форме, однако он представляет интерес лишь после того, как будет реализован в некотором классе.
Некоторые интерфейсы являются чисто абстрактными — у них нет никакого полезного универсального воплощения, и они должны заново реализовываться для каждого нового класса. Тем не менее большая часть интерфейсов может иметь несколько полезных реализаций. В случае нашего интерфейса Attributed можно придумать несколько возможных реализаций, в которых используются различные стратегии для хранения набора атрибутов.
Одна стратегия может быть простой и быстродействующей (если набор содержит малое количество атрибутов); другую можно оптимизировать для работы с наборами редко изменяемых атрибутов; наконец, третья может предназначаться для часто меняющихся атрибутов. Если бы существовал пакет с возможными реализациями интерфейса Attributed, то класс, реализующий этот интерфейс, мог бы воспользоваться одной из них или же предоставить свой собственный вариант.
В качестве примера рассмотрим простую реализацию Attributed, в которой используется вспомогательный класс java.util.Hashtable. Позднее это будет использовано, чтобы реализовать интерфейс Attributed для конкретного набора объектов, наделяемых атрибутами. Прежде всего, класс AttributedImpl выглядит следующим образом:
import java.util.*;
class AttributedImpl implements Attributed
{
protected Hashtable attrTable = new Hashtable();
public void add(Attr newAttr) {
attrTable.put(newAttr.nemeOf(), newAttr);
}
public Attr find(String name) {
return (Attr)attrTable.get(name);
}
public Attr remove(String name) {
return (Attr)attrTable.remove(name);
}
public Enumeration attrs() {
return attrTable.elements();
}
}
В реализации методов AttributedImpl используется класс Hashtable.
При инициализации attrTable создается объект Hashtable, в котором хранятся атрибуты. Большая часть работы выполняется именно классом Hashtable. Класс HashTable использует метод hashCode данного объекта для хеширования. Нам не приходится писать свой метод хеширования, поскольку String уже содержит подходящую реализацию hashCode.
При добавлении нового атрибута объект Attr сохраняется в хеш-таблице, причем имя атрибута используется в качестве ключа хеширования; затем по имени атрибута можно осуществлять поиск и удаление атрибутов из хеш-таблицы.
Метод attrs возвращает значение Enumeration, в котором приведены все атрибуты, входящие в набор. Enumeration является абстрактным классом, определенным в java.util и используемым классами-коллекциями типа Hash table для возвращения списков (см. раздел “Интерфейс Enumeration”). Мы также воспользуемся этим типом, поскольку он предоставляет стандартное средство для возвращения списков в Java. Фактически интерфейс Attributed определяет тип-коллекцию, поэтому применим обычный в таких случаях механизм возврата содержимого коллекции, а именно класс Enumeration. Использование Enumeration имеет ряд преимуществ: стандартные классы-коллекции вроде Hashtable, в которых применяется Enumeration, позволяют упростить реализацию Attributed.