Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Тема 3.10Переопределение методов и поддержка полиморфизма
Пример, приведенный в предыдущем разделе, демонстрирует переопределение методов, но из него трудно понять, насколько богатые возможности представляет данный механизм. Действительно, если переопределение методов используется только для соблюдения соглашений об именовании, то его можно рассматривать как интересную, но почти бесполезную деталь. Однако это не так. Переопределение методов лежит в основе одного из наиболее мощных средств динамического доступа к методам. Динамический доступ к методам – это механизм вызова переопределенных методов, причем выбор конкретного метода может осуществляется не на этапе компиляции, а в процессе выполнения программы. Динамический доступ к методам очень важен, поскольку именно посредством его в языке Java реализован полиморфизм. Давайте начнем с того, что вспомним один очень важный принцип: переменная суперкласса может ссылаться на экземпляр подкласса. В языке Java этот факт используется для вызова переопределенных методов на этапе выполнения. Если переопределенный метод вызывается посредством ссылки на суперкласс, то исполняющая система Java по типу объекта определяет, какой вариант метода следует вызвать, причем выясняет это в процессе работы программы. Если ссылки указывают на различные типы объектов, то вызваны будут разные версии переопределенных методов. Другими словами, вариант переопределенного метода для вызова определяет не тип переменной, а тип объекта, на который она ссылается. Таким образом, если суперкласс содержит метод, переопределенный в подклассе, то вызывается метод, соответствующий тому типу объекта, на который указывает переменная суперкласса. Ниже приведен простой пример, демонстрирующий динамический доступ к методам. Листинг 3.17 // Демонстрация динамического доступа к методам public class Sup { public void who() { System.out.println(" who() in Sup" ); } } public class Sub1 extends Sup { public void who() { System.out.println(" who() in Sub1" ); } } public class Sub2 extends Sup { public void who() { System.out.println(" who() in Sub2" ); } } public class DynDispDemo { public static void main(String args[]) { Sup superOb = new Sup(); Sub1 subOb1 = new Sub1(); Sub2 subOb2 = new Sub2(); Sup supRef; // В каждом из приведенных ниже вызовов конкретный // вариант метода who() определяется на этапе // выполнения программы supRef = superOb; supRef.who(); supRef = subOb1; supRef.who(); supRef = subOb2; supRef.who(); } } В данной программе определен суперкласс Sup и два подкласса, Sub1 и Sub2. В классе Sup определен метод who(), который переопределяется в подклассах. B теле метода main() созданы объекты типа Sup, Subl и Sub2. Там же присутствует ссылка на тип Sup с именем supRef. Затем в методе main () переменной supRef последовательно присваиваются ссылки на объекты различного типа и эти ссылки используются для вызова метода who(). Как видно из выходных данных, вариант метода who(), который должен быть вызван, определяется типом объекта, на который ссылается переменная supRef в момент вызова, а не типом этой переменной. Переопределяемые методы обеспечивают поддержку полиморфизма в языке Java. Полиморфизм в объектно-ориентированных программах имеет большое значение потому, что благодаря ему становится возможным объявить в классе методы, общие для всех подклассов, но позволить подклассам определять специфические реализации всех этих методов или некоторых из них. Переопределение методов – один из способов, которыми в языке Java реализуется принцип полиморфизма " один интерфейс – много методов". Одним из залогов успешного использования полиморфизма является понимание того, что суперклассы и подклассы формируют иерархию в направлении от меньшей специализации к большей. Будучи сформированным корректно, суперкласс предоставляет подклассу все элементы, которые последний может непосредственно использовать. В нем также определяются те методы, которые должны быть по-другому реализованы в дочерних классах. Таким образом, подклассы получают достаточную гибкость, выражающуюся в возможности определять собственные методы, и в то же время оказываются вынуждены реализовывать требуемый интерфейс. Объединяя наследование с переопределением методов, суперкласс определяет общий формат методов, который должен быть использован во всех его подклассах. Для того чтобы лучше понять, насколько мощным является механизм переопределения методов, применим его к классу TwoDShape. В приведенных ранее примерах в каждом классе, дочернем по отношению к классу TwoDShape, определялся метод area(). Теперь мы знаем, что в этом случае имеет смысл включить метод area() в состав класса TwoDShape и позволить каждому подклассу переопределить этот метод, в частности реализовать вычисление площади в зависимости от конкретного типа геометрической фигуры. Такой подход реализован в приведенной ниже программе. Для удобства к классу TwoDShape добавлено поле name, которое упрощает написание демонстрационного кода. Листинг 3.18 // Использование динамического доступа к методу public class TwoDShape { private double width; private double height; private String name; // Конструктор по умолчанию public TwoDShape() { width = height = 0.0; name = " null"; } // Конструктор с параметрами public TwoDShape(double w, double h, String n) { width = w; height = h; name = n; } // Формирование объекта с равными // значениями width и height public TwoDShape(double x, String n) { width = height = x; name = n; } // Формирование объекта на основе другого объекта public TwoDShape(TwoDShape ob) { width = ob.width; height = ob.height; name = ob.name; } // Методы для доступа к переменным width и height public double getWidth() { return width; } public double getHeight() { return height; } public void setWidth(double w) { width = w; } public void setHeight(double h) { height = h; } public String getName() { return name; } public void showDim() { System.out.println(" Width and height are " + width + " and " + height); } // Метод area() определенвклассе TwoDShape public double area() { System.out.println(" area() must be overridden" ); return 0.0; } } // Подкласс класса TwoDShape для представления треугольников public class Triangle extends TwoDShape { private String style; // Конструктор по умолчанию public Triangle() { super(); style = " null"; } // Конструкторкласса Triangle public Triangle(String s, double w, double h) { super(w, h, " triangle" ); style = s; } // формирование равнобедренных треугольников public Triangle(double x) { super(x, " triangle" ); // Вызов конструктора суперкласса style = " isosceles"; } // формирование объекта на основе другого объекта public Triangle(Triangle ob) { super(ob); // Передачаобъектаконструктору TwoDShape style = ob.style; } // Переопределениеметода area() длякласса Triangle public double area() { return getWidth() * getHeight() / 2; } void showStyle() { System.out.println(" Triangle is " + style); } } // Подкласс класса TwoDShape для представления прямоугольников public class Rectangle extends TwoDShape { // Конструктор по умолчанию public Rectangle() { super(); } // Конструкторкласса Rectangle public Rectangle(double w, double h) { super(w, h, " rectangle" ); // Вызов конструктора суперкласса } // Формированиеквадрата public Rectangle(double x) { super(x, " rectangle" ); // Вызов конструктора суперкласса } // Формирование объекта на основе другого объекта public Rectangle(Rectangle ob) { super(ob); // Передачаобъектаконструктору TwoDShape } public boolean isSquare() { if (getWidth() == getHeight()) { return true; } return false; } // Переопределениеметода area() длякласса Rectangle public double area() { return getWidth() * getHeight(); } }
public class DynShapes { public static void main(String args[]) { TwoDShape shapes[] = new TwoDShape[5]; shapes[0] = new Triangle(" right", 8.0, 12.0); shapes[1] = new Rectangle(10); shapes[2] = new Rectangle(10, 4); shapes[3] = new Triangle(7.0); shapes[4] = new TwoDShape(10, 20, " generic" ); for (int i = 0; i < shapes.length; i++) { System.out.println(" object is " + shapes[i].getName()); // Требуемый вариант area() определяется // для каждой геометрической фигуры System.out.println(" Area is " + shapes[i].area()); System.out.println(); } } } Рассмотрим код программы более подробно. Теперь, как и предполагалось написании программы, метод area() входит в состав класса TwoDShape переопределяется в классах Triangle и Rectangle. В классе TwoDShape метод area() играет роль заглушки и лишь информирует пользователя о том, что метод должен быть переопределен в подклассе. При каждом переопределении метода area() в нем реализуются средства, необходимые для того типа объекта, который представляется подклассом. Таким образом, если вам надо будет реализовать класс, представляющий эллипс, вам придется переопределить метод area() так, чтобы он вычислял площадь данной фигуры. В рассматриваемой программе есть еще одна важная особенность. Обратите внимание на то, что в методе main() фигуры объявлены как массив объектов TwoDShape. Однако на самом деле элементами массива являются ссылки на объекты Triangle, Rectangle и TwoDShape. Это допустимо, поскольку переменная суперкласса может быть ссылкой на объект подкласса. В программе организован перебор элементов массива в цикле и вывод информации о каждом объекте. Несмотря на то что пример достаточно прост, он иллюстрирует как возможности наследования, так и переопределения методов. Тип объекта, на который указывает переменная суперкласса, определяется на этапе выполнения программы и обрабатывается соответствующим образом. Если объект является дочерним по отношению к TwoDShape, то его площадь вычисляется путем вызова метода area(). Интерфейс для данной операции общий и не зависит от того, с какой геометрической фигурой мы имеем дело в каждый конкретный момент. Популярное:
|
Последнее изменение этой страницы: 2016-05-30; Просмотров: 672; Нарушение авторского права страницы