Чтение онлайн

на главную - закладки

Жанры

Философия Java3

Эккель Брюс

Шрифт:

//• polymorphism/music/instrument java

package polymorphism.music,

import static net mindview.util.Print.*,

class Instrument {

public void play(Note n) {

print("Instrument.pi ay(Г);

}

}

III ~

//• polymorphism/music/Wind java package polymorphism.music;

// Объекты Wind также являются объектами Instrument, II поскольку имеют тот же интерфейс: public class Wind extends Instrument { // Переопределение метода интерфейса public void pi ay(Note n) {

System out pri ntl n( "Wind playO " + n),

}

} III-

II polymorphism/music/Music.java II

Наследование и восходящее преобразование package polymorphism music,

public class Music {

public static void tune(Instrument i) { // ...

i.play(Note.MIDDLE_C),

}

public static void main(String[] args) {

Wind flute = new WindO

tune(flute). // Восходящее преобразование

}

} /* Output

Wind playO MIDDLE_C

*/// -

Метод Music.tune получает ссылку на Instrument, но последняя также может указывать на объект любого класса, производного от Instrument. В методе main ссылка на объект Wind передается методу tune без явных преобразований. Это нормально; интерфейс класса Instrument должен существовать и в классе Wind, поскольку последний был унаследован'от Instrument. Восходящее преобразование от Wind к Instrument способно «сузить» этот интерфейс, но не сделает его «меньше», чем полный интерфейс класса Instrument.

Потеря типа объекта

Программа Music.java выглядит немного странно. Зачем умышленно игнорировать фактический тип объекта? Именно это мы наблюдаем при восходящем преобразовании, и казалось бы, программа стала яснее, если бы методу tune передавалась ссылка на объект Wind. Но при этом мы сталкиваемся с очень важным обстоятельством: если поступить подобным образом, то потом придется писать новый метод tune для каждого типа Instrument, присутствующего в системе. Предположим, что в систему были добавлены новые классы Stringed и Brass:

// polymorphi sm/musi c/Musi c2.java // Перегрузка вместо восходящего преобразования package polymorphism.music, import static net.mindview util Print *;

class Stringed extends Instrument {

public void play(Note n) {

pri nt ("Stri nged.pl ay " + n):

}

}

class Brass extends Instrument {

public void play(Note n) {

printC'Brass playO " + n),

}

}

public class Music2 {

public static void tune(Wind i) { i.play(Note MIDDLE_C),

}

public static void tune(Stringed i) { i.play(Note MIDDLE'C);

}

public static void tune(Brass i) { i play(Note.MIDDLE_C);

}

public static void main(String[] args) {

Wind flute = new Wind,

Stringed violin = new StnngedO.

Brass frenchHorn = new BrassO.

tune(flute), // Без восходящего преобразования

tune(violin);

tune(frenchHorn).

}

} /* Output

Wind playO MIDDLE_C

Stringed.pi ayО MIDDLE_C

Brass pi ayО MIDDLE_C *///-

Программа работает, но у нее есть огромный недостаток: для каждого нового Instrument

приходится писать новый, зависящий от конкретного типа метод tune. Объем программного кода увеличивается, а при добавлении нового метода (такого, как tune) или нового типа инструмента придется выполнить немало дополнительной работы. А если учесть, что компилятор не выводит сообщений об ошибках, если вы забудете перегрузить один из ваших методов, весь процесс работы с типами станет совершенно неуправляемым.

Разве не лучше было бы написать единственный метод, в аргументе которого передается базовый класс, а не один из производных классов? Разве не удобнее было бы забыть о производных классах и написать обобщенный код для базового класса?

Именно это и позволяет делать полиморфизм. Однако большинство программистов с опытом работы на процедурных языках при работе с полиморфизмом испытывают некоторые затруднения.

Особенности

Сложности с программой Music.java обнаруживаются после ее запуска. Она выводит строку Wind.play. Именно это и требуется, но не понятно, откуда берется такой результат. Взгляните на метод tune:

public static void tune(Instrument i) {

//

i play(Note.MIDDLE_C),

}

Метод получает ссылку на объект Instrument. Как компилятор узнает, что ссылка на Instrument в данном случае указывает на объект Wind, а не на Brass или Stringed? Компилятор и не знает. Чтобы в полной мере разобраться в сути происходящего, необходимо рассмотреть понятие связывания (binding).

Связывание «метод-вызов»

Присоединение вызова метода к телу метода называется связыванием. Если связывание проводится перед запуском программы (компилятором и компоновщиком, если он есть), оно называется ранним связыванием (early binding). Возможно, ранее вам не приходилось слышать этот термин, потому что в процедурных языках никакого выбора связывания не было. Компиляторы С поддерживают только один тип вызова — раннее связывание.

Неоднозначность предыдущей программы кроется именно в раннем связывании: компилятор не может знать, какой метод нужно вызывать, когда у него есть только ссылка на объект Instrument

Проблема решается благодаря позднему связыванию (late binding), то есть связыванию, проводимому во время выполнения программы, в зависимости от типа объекта. Позднее связывание также называют динамическим (dynamic) или связыванием на стадии выполнения (runtime binding). В языках, реализующих позднее связывание, должен существовать механизм определения фактического типа объекта во время работы программы, для вызова подходящего метода. Иначе говоря, компилятор не знает тип объекта, но механизм вызова методов определяет его и вызывает соответствующее тело метода. Механизм позднего связывания зависит от конкретного языка, но нетрудно предположить, что для его реализации в объекты должна включаться какая-то дополнительная информация.

Поделиться:
Популярные книги

Мастер 3

Чащин Валерий
3. Мастер
Фантастика:
героическая фантастика
попаданцы
аниме
5.00
рейтинг книги
Мастер 3

Дважды одаренный. Том II

Тарс Элиан
2. Дважды одаренный
Фантастика:
городское фэнтези
альтернативная история
аниме
5.00
рейтинг книги
Дважды одаренный. Том II

Печать Пожирателя

Соломенный Илья
1. Пожиратель
Фантастика:
попаданцы
аниме
сказочная фантастика
фэнтези
5.00
рейтинг книги
Печать Пожирателя

Древесный маг Орловского княжества 13

Павлов Игорь Васильевич
13. Орловское княжество
Фантастика:
аниме
фэнтези
попаданцы
5.00
рейтинг книги
Древесный маг Орловского княжества 13

Проклятый Лекарь. Том 2

Молотов Виктор
2. Анатомия Тьмы
Фантастика:
фэнтези
попаданцы
7.00
рейтинг книги
Проклятый Лекарь. Том 2

Последний Герой. Том 3

Дамиров Рафаэль
3. Последний герой
Фантастика:
попаданцы
альтернативная история
фантастика: прочее
5.00
рейтинг книги
Последний Герой. Том 3

Инженер Петра Великого

Гросов Виктор
1. Инженер Петра Великого
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Инженер Петра Великого

Чехов книга 3

Гоблин (MeXXanik)
3. Адвокат Чехов
Фантастика:
попаданцы
альтернативная история
аниме
6.00
рейтинг книги
Чехов книга 3

Группа крови на рукаве. Том 2

Вязовский Алексей
2. ГК
Фантастика:
боевая фантастика
альтернативная история
постапокалипсис
5.00
рейтинг книги
Группа крови на рукаве. Том 2

Наследник для дона мафии

Тоцка Тала
2. Наследники мафии
Любовные романы:
остросюжетные любовные романы
эро литература
5.00
рейтинг книги
Наследник для дона мафии

Поводырь

Щепетнов Евгений Владимирович
3. Ботаник
Фантастика:
фэнтези
6.17
рейтинг книги
Поводырь

Сталин

Радзинский Эдвард Станиславович
3. Загадки жизни и смерти
Проза:
историческая проза
7.36
рейтинг книги
Сталин

Александр Агренев. Трилогия

Кулаков Алексей Иванович
Александр Агренев
Фантастика:
альтернативная история
9.17
рейтинг книги
Александр Агренев. Трилогия

Кодекс Охотника XXVIII

Винокуров Юрий
28. Кодекс Охотника
Фантастика:
фэнтези
боевая фантастика
попаданцы
5.00
рейтинг книги
Кодекс Охотника XXVIII