34

34


}
public String whatO { return "Wind"; } public void adjustO {}
}
class Percussion extends Instrument { public void play(Note n) {
printC'Percussion playO " + n).
}
public String whatO { return "Percussion", } public void adjustO {}
}
class Stringed extends Instrument { public void play(Note n) {
print ("Stringed playO " + n),
}
public String whatO { return "Stringed". } public void adjustO {}
}
class Brass extends Wind {
public void play(Note n) {
printCBrass.playO " + n);
}
public void adjustO { printC'Brass adjustO"), }
}
class Woodwind extends Wind { public void play(Note n) {
print("Woodwind playО " + n);
}
public String whatO { return "Woodwind", }
}
public class Music4 {
// Работа метода не зависит от фактического типа объекта. // поэтому типы, добавленные в систему, будут работать правильно:
static void tune(Instrument i) {
//
i.piay(Note MIDDLE_C),
}
static void tuneAll (Instrument!!] e) { for(Instrument i e) tune(i).
}
public static void main(String[] args) {
// Восходящее преобразование при добавлении в массив Instruments orchestra = { new WindO. new PercussionO. new StringedO. new BrassO. new WoodwindО
}.
tuneAl1(orchestra).
}
} /* Output Wind.pi ayО MIDDLE_C Percussion playO MIDDLE_C Stringed playO MIDDLE_C Brass playO MIDDLE_C Woodwind pi ayО MIDDLE_C */// ~
Как видите, объем изменений минимален.
Создавать абстрактные классы и методы полезно, так как они подчеркивают абстрактность класса, а также сообщают и пользователю класса, и компилятору, как следует с ним обходиться. Кроме того, абстрактные классы играют полез­ную роль при переработке программ, потому что они позволяют легко переме­щать общие методы вверх по иерархии наследования.
Интерфейсы
Ключевое слово interface становится следующим шагом на пути к абстракции. Оно используется для создания полностью абстрактных классов, вообще не имеющих реализации. Создатель интерфейса определяет имена методов, списки аргументов и типы возвращаемых значений, но не тела методов.
Ключевое слово interface фактически означает: «Именно так должны выгля­деть все классы, которыереализуютданный интерфейс». Таким образом, лю­бой код, использующий конкретный интерфейс, знает только то, какие методы вызываются для этого интерфейса, но не более того. Интерфейс определяет своего рода «протокол взаимодействия» между классами.
Однако интерфейс представляет собой нечто большее, чем абстрактный класс в своем крайнем проявлении, потому что он позволяет реализовать подо­бие «множественного наследования» С++: иначе говоря, создаваемый класс мо­жет быть преобразован к нескольким базовым типам.
Чтобы создать интерфейс, используйте ключевое слово interface вместо class. Как и в случае с классами, вы можете добавить перед словом interface специфи­катор доступа public (но только если интерфейс определен в файле, имеющем то же имя) или оставить для него дружественный доступ, если он будет исполь­зоваться только в пределах своего пакета. Интерфейс также может содержать поля, но они автоматически являются статическими (static) и неизменными (final).
Для создания класса, реализующего определенный интерфейс (или группу интерфейсов), используется ключевое слово implements. Фактически оно озна­чает: «Интерфейс лишь определяет форму, а сейчас будет показано, как этора­ботает». В остальном происходящее выглядит как обычное наследование. Рас­смотрим реализацию на примере иерархии классов Instrument:



Классы Woodwind и Brass свидетельствуют, что реализация интерфейса представляет собой обычный класс, от которого можно создавать производ­ные классы.
При описании методов в интерфейсе вы можете явно объявить их открыты­ми (public), хотя они являются таковыми даже без спецификатора. Однако при реализации интерфейса его методыдолжныбыть объявлены как public. В про­тивном случае будет использоваться доступ в пределах пакета, а это приведет к уменьшению уровня доступа во время наследования, что запрещается компи­лятором Java.
Все сказанное можно увидеть в следующем примере с объектами Instrument. Заметьте, что каждый метод интерфейса ограничивается простым объявлением; ничего большего компилятор не" разрешит. Вдобавок ни один из методов интер­фейса Instrument не объявлен со спецификатором public, но все методы автома­тически являются открытыми:

// interfaces/music5/Music5.java
// Интерфейсы.
package interfaces.music5;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
interface Instrument {
// Константа времени компиляции:
int VALUE = 5; // является и static, и final
// Определения методов недопустимы:
void play(Note n); // Автоматически объявлен как public
void adjustO;
}
class Wind implements Instrument { public void play(Note n) {
print(this + ".playO " + n);
}
public String toStringO { return "Wind"; } public void adjustO { print(this + ".adjustO"); }
}
class Percussion implements Instrument { public void play(Note n) {
print(this + ".playO " + n),
}
public String toStringO { return "Percussion"; } public void adjustO { print(this + " adjustO"); }
}
class Stringed implements Instrument { public void play(Note n) {
print(this + ".playO " + n);
}
public String toStringO { return "Stringed"; } public void adjustO { print(this + ".adjustO"); }
}
class Brass extends Wind {
public String toStringO { return "Brass"; }
}
class Woodwind extends Wind {
public String toStringO { return "Woodwind"; }
}
public class Music5 {
// Работа метода не зависит от фактического типа объекта. // поэтому типы, добавленные в систему, будут работать правильно: static void tune(Instrument i) { // .
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instruments e) { for(Instrument i : e) tune(i);
}
public static void main(String[] args) {

// Восходящее преобразование при добавлении в массив. Instrument!!] orchestra = { new WindO. new PercussionO. new StringedO, new BrassO. new WoodwindО
}.
tuneAll(orchestra),
}
} /* Output: Wind.pi ayО MIDDLE_C Percussion.playO MIDDLE_C Stringed.pi ayО MIDDLE_C Brass.pi ayО MIDDLE_C Woodwind pi ayО MIDDLE_C */// ~
В этой версии присутствует еще одно изменение: метод what() был заменен на toString(). Так как метод toString() входит в корневой класс Object, его присут­ствие в интерфейсе не обязательно.
Остальной код работает так же, как прежде. Неважно, проводите ли вы пре­образование к «обычному» классу с именем Instrument, к абстрактному классу с именем Instrument или к интерфейсу с именем Instrument — действие будет одинаковым. В методе tune() ничто не указывает на то, является класс Instrument «обычным» или абстрактным, или это вообще не класс, а интерфейс.
Отделение интерфейса от реализации
В любой ситуации, когда метод работает с классом вместо интерфейса, вы огра­ничены использованием этого класса или его субклассов. Если метод должен быть применен к классу, не входящему в эту иерархию, — значит, вам не повезло. Интерфейсы в значительной мере ослабляют это ограничение. В результате код становится более универсальным, пригодным для повторного использова­ния.
Представьте, что у нас имеется класс Processor с методами name() и process(). Последний получает входные данные, изменяет их и выдает результат. Базовый класс расширяется для создания разных специализированных типов Processor. В следующем примере производные типы изменяют объекты String (обратите внимание: ковариантными могут быть возвращаемые значения, но не типы ар­гументов):
//• interfaces/classprocessor/Apply.java package interfaces classprocessor; import java.util.*;
import static net.mindview.util.Print.*;
class Processor {
public String nameО {
return getClass().getSimpleName();
}
Object process(Object input) { return input; }
class Upcase extends Processor {
String process(Object input) { // Ковариантный возвращаемый тип return ((String)input) toUpperCase(),
class Downcase extends Processor { String process(Object input) {
return ((String)input) toLowerCase(),
class Splitter extends Processor { String process(Object input) {
// Аргумент splitO используется для разбиения строки return Arrays toString(((String)input) splitC ")),
public class Apply {
public static void process(Processor p. Object s) { print ("Используем Processor " + p nameO), print(p.process(s));
}
public static String s =
"Disagreement with beliefs is by definition incorrect"; public static void main(String[] args) { process(new UpcaseO, s); process(new Downcase(), s); process(new SplitterO, s),
}
} /* Output:
Используем Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT Используем Processor Downcase disagreement with beliefs is by definition incorrect Используем Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect] *///-
Метод Apply.process() получает любую разновидность Processor, применяет ее к Object, а затем выводит результат. Метод split() является частью класса String. Он получает объект String, разбивает его на несколько фрагментов по ограничи­телям, определяемым переданным аргументом, и возвращает String[]. Здесь он используется как более компактный способ создания массива String.
Теперь предположим, что вы обнаружили некое семейство электронных фильтров, которые тоже было бы уместно использовать с методом Apply. process():
// interfaces/filters/Waveform java package interfaces.filters.
public class Waveform {
private static long counter;
private final long id = counter++; public String toStringO { return "Waveform " + id. }} Hi­ll-interfaces/filters/Filter java package interfaces filters,
public class Filter {
public String nameO {
return getClassO getSimpleName().
}
public Waveform process(Waveform input) { return input; }} III ~// interfaces/filters/LowPass java package interfaces filters,
public class LowPass extends Filter { double cutoff;
public LowPass(double cutoff) { this.cutoff = cutoff; } public Waveform process(Waveform input) {
return input; II Фиктивная обработка
}
} Hi­ll ■ i nterfaces/fi 1ters/Hi ghPass.java package interfaces.filters;
public class HighPass extends Filter { double cutoff;
public HighPass(double cutoff) { this.cutoff = cutoff; } public Waveform process(Waveform input) { return input. } } ///.-
// interfaces/filters/BandPass java package interfaces filters;
public class BandPass extends Filter { double lowCutoff. highCutoff; public BandPass(double lowCut. double highCut) { lowCutoff = lowCut; highCutoff = highCut;
}
public Waveform process(Waveform input) { return input; } }III-Класс Filter содержит те же интерфейсные элементы, что и Processor, но, по­скольку он не является производным от Processor (создатель класса Filter и не по­дозревал, что вы захотите использовать его как Processor), он не может исполь­зоваться с методом Apply.process(), хотя это выглядело бы вполне естественно. Логическая привязка между Apply.process() и Processor оказывается более силь­ной, чем реально необходимо, и это обстоятельство препятствует повторному ис­пользованию кода Apply.process(). Также обратите внимание, что входные и вы­ходные данные относятся к типу Waveform.
Но, если преобразовать класс Processor в интерфейс, ограничения ослабля­ются и появляется возможность повторного использования Apply.process(). Об­новленные версии Processor и Apply выглядят так:
//: interfaces/interfaceprocessor/Processor.java package interfaces interfaceprocessor;
public interface Processor { String nameO;
Object process(Object input), } ///-
//. interfaces/interfaceprocessor/Apply.java package i nterfaces.i nterfaceprocessor, import static net mindview.util.Print.*:
public class Apply {
public static void process(Processor p. Object s) { print ("Using Processor " + p.nameO): print(p.process(s)):
}
} ///:-
В первом варианте повторного использования кода клиентские программи­сты пишут свои классы с поддержкой интерфейса:
//: interfaces/interfaceprocessor/StringProcessor.java package i nterfaces.i nterfaceprocessor; import java.util.*;
public abstract class StringProcessor implements Processor! public String nameO {
return getClassO getSimpleNameO;
}
public abstract String process(Object input); public static String s =
"If she weighs the same as a duck, she's made of wood"; public static void main(String[] args) { Apply, process (new UpcaseO, s); Apply, process (new DowncaseO, s); Apply, process (new SplitterO. s);