Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 46 47 48 49 50 51 52 53 54 ... 132

При наследовании базовый класс вовсе не обязан быть абстрактным или «реальным» (без абстрактных методов). Если наследование действительно осуществляется не от интерфейса, то среди прямых «предков» класс может быть только один — все остальные должны быть интерфейсами. Имена интерфейсов перечисляются вслед за ключевым словом implements и разделяются запятыми. Интерфейсов может быть сколько угодно, причем к ним можно проводить восходящее преобразование. Следующий пример показывает, как создать новый класс на основе реального класса и нескольких интерфейсов:

//: 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), // Используем объект в качестве типа CanFight u(h). // Используем объект в качестве типа CanSwim v(h). // Используем объект в качестве типа CanFly w(h), // Используем объект в качестве ActionCharacter

}

} ///-

Мы видим, что класс Него сочетает реальный класс 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 представляет собой простое расширение Monster, в результате которого образуется новый интерфейс. Он реализуется классом DragonZilla.

Синтаксис, использованный в интерфейсе 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

InterfaceCollisi on java;24- интерфейсы 13 и II несовместимы; оба определяют f(). но с различными возвращаемыми типами

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

Интерфейсы как средство адаптации

Одной из самых убедительных причин для использования интерфейсов является возможность определения нескольких реализаций для одного интерфейса. В простых ситуациях такая схема принимает вид метода, который при вызове передается интерфейсу; от вас потребуется реализовать интерфейс и передать объект методу.

Соответственно, интерфейсы часто применяются в архитектурном паттерне «Стратегия». Вы пишете метод, выполняющий несколько операций; при вызове метод получает интерфейс, который тоже указываете вы. Фактически вы говорите: «Мой метод может использоваться с любым объектом, удовлетворяющим моему интерфейсу». Метод становится более гибким и универсальным.

Например, конструктор класса Java SE5 Scanner получает интерфейс Readable. Анализ показывает, что Readable не является аргументом любого другого метода из стандартной библиотеки Java — этот интерфейс создавался исключительно для Scanner, чтобы его аргументы не ограничивались определенным классом. При таком подходе можно заставить Scanner работать с другими типами. Если вы хотите создать новый класс, который может использоваться со Scanner, реализуйте в нем интерфейс Readable:

//. interfaces/RandomWords java

// Реализация интерфейса для выполнения требований метода

import java nio.*;

import java util.*,

public class RandomWords implements Readable { private static Random rand = new Random(47); private static final char[] capitals =

"ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final char[] lowers =

"abcdefghijklmnopqrstuvwxyz".toCharArrayO; private static final char[] vowels =

"aeiou" toCharArrayO; private int count,

public RandomWords(int count) { this.count = count: } public int read(CharBuffer cb) { if(count-- == 0)

return -1; // Признак конца входных данных cb.append(capi tals[rand.nextInt(capi ta1s.1 ength) ]); for(int i = 0; i < 4; i++) {

cb.append(vowels[rand.nextInt(vowels.1ength)]); cb append(lowers[rand.nextInt(lowers.length)]),

}

cb.append(" "),

return 10; // Количество присоединенных символов

}

public static void main(String[] args) {

Scanner s = new Scanner(new RandomWords(10)); while(s.hasNextO)

System.out.println(s.nextO);

}

} /* Output:

Yazeruyac

Fowenucor Goeazimom Raeuuacio Nuoadesiw Hageaikux Ruqicibui. Numasetih Kuuuuozog Waqizeyoy */// ~

Интерфейс Readable требует только присутствия метода read(). Метод read() либо добавляет данные в аргумент CharBuffer (это можно сделать несколькими способами; обращайтесь к документации CharBuffer), либо возвращает -1 при отсутствии входных данных.

Допустим, у нас имеется класс, не реализующий интерфейс Readable, — как заставить его работать с Scanner? Перед вами пример класса, генерирующего вещественные числа:

// interfaces/RandomDoubles java import java util *;

public class RandomDoubles {

private static Random rand = new Random(47), public double next О { return rand nextDouble(), } public static void main(String[] args) {

RandomDoubles rd = new RandomDoubles(), for(int i = 0, l < 7. i ++)

System out print(rd next О + " "),

}

} /* Output

0 7271157860730044 0 5309454508634242 0 16020656493302599 0 18847866977771732

0 5166020801268457 0 2678662084200585 0 2613610344283964 *///.-

Мы снова можем воспользоваться схемой адаптера, но на этот раз адаптируемый класс создается наследованием и реализацией интерфейса Readable. Псевдомножественное наследование, обеспечиваемое ключевым словом interface, позволяет создать новый класс, который одновременно является и Random-Doubles, и Readable:

1 ... 46 47 48 49 50 51 52 53 54 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий