Главное отличие 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
//: 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; // Происходит исключение
}
} ///:-