67

67


class Mill {

Grain process О { return new GrainO; }

}

class WheatMill extends Mill {

Wheat process О { return new WheatO; }

}

public class CovariantReturn {

public static void main(String[] args) { Mill m = new Mi 11(); Grain g = m.processO; System out println(g); m = new WheatMi 110; g = m process О, System out.println(g);

}

} /* Output Grain Wheat */// ~

Главное отличие Java SE5 от предыдущих версий Java заключается в том, что старые версии заставляли переопределение process() возвращать Grain вме­сто Wheat, хотя тип Wheat, производный от Grain, является допустимым возвра­щаемым типом. Ковариантность возвращаемых типов позволяет вернуть более специализированный тип Wheat.

Разработка с наследованием

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

Лучше для начала использовать композицию, пока вы точно не уверены в том, какой именно механизм следует выбрать. Композиция не стесняет разра­ботку рамками иерархии наследования. К тому же механизм композиции более гибок, так как он позволяет динамически выбирать тип (а следовательно, и по­ведение), тогда как наследование требует, чтобы точный тип был известен уже во время компиляции. Следующий пример демонстрирует это:

// polymorphi sm/Transmogrify.java // Динамическое изменение поведения объекта // с помощью композиции (шаблон проектирования «Состояние») • import static net.mindview.util.Print.*;

class Actor {

public void act О {}

}

class HappyActor extends Actor {

public void actO { pri nt ("HappyActor"), }

class SadActor extends Actor {

public void act() { printCSadActor"). }

}

class Stage {

private Actor actor = new HappyActor(); public void changeO { actor = new SadActorO. } public void performPlayO { actor.act(), }

}

public class Transmogrify {

public static void main(String[] args) { Stage stage = new StageO; stage. performPlayO; stage. changeO; stage. performPlayO;

}

} /* Output-

HappyActor

SadActor

*///:-

Объект Stage содержит ссылку на объект Actor, которая инициализируется объектом HappyActor. Это значит, что метод performPlayO имеет определенное поведение. Но так как ссылку на объект можно заново присоединить к другому объекту во время выполнения программы, ссылке actor назначается объект SadActor, и после этого поведение метода performPlayO изменяется. Таким обра­зом значительно улучшается динамика поведения на стадии выполнения про­граммы. С другой стороны, переключиться на другой способ наследования во время работы программы невозможно; иерархия наследования раз и навсе­гда определяется в процессе компиляции программы.

Нисходящее преобразование и динамическое определение типов

Так как при проведениивосходящего преобразования(передвижение вверх по иерархии наследования) теряется информация, характерная для определен­ного типа, возникает естественное желание восстановить ее с помощьюнисхо­дящего преобразования.Впрочем, мы знаем, что восходящее преобразование аб­солютно безопасно; базовый класс не может иметь «больший» интерфейс, чем производный класс, и поэтому любое сообщение, посланное базовому классу, гарантированно дойдет до получателя. Но при использовании нисходящего преобразования вы не знаете достоверно, что фигура (например) в действи­тельности является окружностью. С такой же вероятностью она может оказать­ся треугольником, прямоугольником или другим типом.

Должен существовать какой-то механизм, гарантирующий правильность нисходящего преобразования; в противном случае вы можете случайно исполь­зовать неверный тип, послав ему сообщение, которое он не в состоянии при­нять. Это было бы небезопасно.

В некоторых языках (подобных С++) для проведения безопасного нисхо­дящего преобразования типов необходимо провести специальную операцию, но в Javaкаждое преобразованиеконтролируется! Поэтому, хотя внешне все вы­глядит как обычное приведение типов в круглых скобках, во время выполнения программы это преобразование проходит проверку на фактическое соответст­вие типу. Если типы не совпадают, происходит исключение ClassCastException. Процесс проверки типов во время выполнения программы называетсядинами­ческим определением типов(run-time type identification, RTTI). Следующий пример демонстрирует действие RTTI:

//: polymorphi sm/RTTI java

// Нисходящее преобразование и динамическое определение типов (RTTI)

// {ThrowException}

class Useful {

public void f() {} public void g() {}

}

class MoreUseful extends Useful { public void f() {} public void g() {} public void u() {} public void v() {} public void w() {}

}

public class RTTI {

public static void main(String[] args) { Useful[] x = {

new Useful О. new MoreUsefulО

}:

x[0].f(): x[l] g().

// СТадия компиляции- метод не найден в классе Useful• //! x[l].u().

((MoreUseful)х[1]) u(); // Нисх преобразование /RTTI ((MoreUseful)x[0]).u0; // Происходит исключение

}

} ///:-

Класс MoreUseful расширяет интерфейс класса Useful. Но благодаря наследо­ванию он также может быть преобразован к типу Useful. Вы видите, как это про­исходит, при инициализации массива х в методе main(). Так как оба объекта в массиве являются производными от Useful, вы можете послать сообщения (вызвать методы) f() и д() для обоих объектов, но при попытке вызова метода и() (который существует только в классе MoreUseful) вы получите сообщение об ошибке компиляции.

Чтобы получить доступ к расширенному интерфейсу объекта MoreUseful, ис­пользуйте нисходящее преобразование. Если тип указан правильно, все прой­дет успешно; иначе произойдет исключение ClassCastException. Вам не пона­добится писать дополнительный код для этого исключения, поскольку оно указывает на общую ошибку, которая может произойти в любом месте про­граммы.

Впрочем, RTTI не сводится к простой проверке преобразований. Например, можно узнать, с каким типом вы имеете дело,прежде чемпроводить нисходя­щее преобразование. Глава 11 полностью посвящена изучению различных ас­пектов динамического определения типов Java.

Резюме

Полиморфизм означает «многообразие форм». В объектно-ориентированном программировании базовый класс предоставляет общий интерфейс, а различ­ные версии динамически связываемых методов — разные формы использова­ния интерфейса.

Report Page