Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
GoslingJava2.doc
Скачиваний:
128
Добавлен:
23.02.2016
Размер:
2.39 Mб
Скачать

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.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]