Философия Java3
Шрифт:
//: interfaces/Adventure java
// Использование нескольких интерфейсов.
interface CanFight { void fightO,
}
interface CanSwim { void swimO,
}
interface CanFly { void fly.
}
class ActionCharacter {
public void fightO {}
}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly { public void swimO {}
public void fly {}
}
public class Adventure {
public static void t(CanFight x) { x fightO; } public static void u(CanSwim x) { x swimO, } public static void v(CanFly x) { x fly; } public static void w(ActionCharacter x) { x.fightO. } public static void main(String[] args) { Hero h = new HeroO;
t(h), //
}
} ///-
Мы видим, что класс Него сочетает реальный класс ActionCharacter с интерфейсами CanFight, CanSwim и CanFly. При объединении реального класса с интерфейсами на первом месте должен стоять реальный класс, а за ним следуют интерфейсы (иначе компилятор выдаст ошибку).
Заметьте, что объявление метода fight в интерфейсе CanFight совпадает с тем, что имеется в классе ActionCharacter, и поэтому в классе Него нет определения метода fight. Интерфейсы можно расширять, но при этом получается другой интерфейс. Необходимым условием для создания объектов нового типа является наличие всех определений. Хотя класс Него не имеет явного определения метода fight, это определение существует в классе ActionCharacter, что и делает возможным создание объектов класса Него.
Класс Adventure содержит четыре метода, которые принимают в качестве аргументов разнообразные интерфейсы и реальный класс. Созданный объект Него передается всем этим методам, а это значит, что выполняется восходящее преобразование объекта к каждому интерфейсу по очереди. Система интерфейсов Java спроектирована так, что она нормально работает без особых усилий со стороны программиста.
Помните, что главная причина введения в язык интерфейсов представлена в приведенном примере: это возможность выполнять восходящее преобразование к нескольким базовым типам. Вторая причина для использования интерфейсов совпадает с предназначением абстрактных классов: запретить програм-мисту-клиенту создание объектов этого класса.
Возникает естественный вопрос: что лучше — интерфейс или абстрактный класс? Если можно создать базовый класс без определений методов и переменных-членов, выбирайте именно интерфейс, а не абстрактный класс. Вообще говоря, если известно, что нечто будет использоваться как базовый класс, первым делом постарайтесь сделать это «нечто» интерфейсом.
Расширение интерфейса через наследование
Наследование позволяет легко добавить в интерфейс объявления новых методов, а также совместить несколько интерфейсов в одном. В обоих случаях получается новый интерфейс, как показано в следующем примере:
//• interfaces/HorrorShow java
// Расширение интерфейса с помощью наследования
interface Monster { void menace,
}
interface DangerousMonster extends Monster { void destroy;
}
interface Lethal { void kill;
}
class DragonZilla implements DangerousMonster { public void menaceО {} public void destroyО {}
}
interface Vampire extends DangerousMonster, Lethal { void drinkBloodO;
}
class VeryBadVampire implements Vampire { public void menaceO {} public void destroyО {} public void killO {} public void drinkBloodO {}
}
public class HorrorShow {
static void u(Monster b) { b.menaceO; } static void v(DangerousMonster d) { d. menaceO, d.destroyО;
}
static void w(Lethal 1) { 1 killO; } public static void main(String[] args) {
DangerousMonster barney = new DragonZi11a; u(barney); v(barney);
Vampire vlad = new VeryBadVampire; u(vlad), v(vlad); w(vlad);
}
} ///:-
DangerousMonster
Синтаксис, использованный в интерфейсе Vampire, работает только при наследовании интерфейсов. Обычно ключевое слово extends может использоваться всего с одним классом, но, так как интерфейс можно составить из нескольких других интерфейсов, extends подходит для написания нескольких имен интерфейсов при создании нового интерфейса. Как нетрудно заметить, имена нескольких интерфейсов разделяются при этом запятыми.
Конфликты имен при совмещении интерфейсов
При реализации нескольких интерфейсов может возникнуть небольшая проблема. В только что рассмотренном примере интерфейс CanFight и класс Action-Character имеют идентичные методы void fight. Хорошо, если методы полностью тождественны, но что, если они различаются по сигнатуре или типу возвращаемого значения? Рассмотрим такой пример:
//• i interfaces/InterfaceColli si on java package interfaces;
interface II { void f; }
interface 12 { int f(int i); }
interface 13 { int f. }
class С { public int f { return 1; } }
class C2 implements II. 12 { public void f {}
public int f(int i) { return 1; } // перегружен
}
class C3 extends С implements 12 {
public int f(int i) { return 1; } // перегружен
}
class C4 extends С implements 13 { // Идентичны, все нормально; public int f { return 1; }
}
// Методы различаются только по типу возвращаемого значения; //! class С5 extends С implements II {} //! interface 14 extends II. 13 {} ///;-
Трудность возникает из-за того, что переопределение, реализация и перегрузка образуют опасную «смесь». Кроме того, перегруженные методы не могут различаться только возвращаемыми значениями. Если убрать комментарий в двух последних строках программы, сообщение об ошибке разъясняет суть происходящего:
InterfaceCollisi on.java.23 f в С не может реализовать f в II; попытка использовать несовместимые возвращаемые типы обнаружено: int требуется- void