Текст книги "Java: руководство для начинающих (ЛП)"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 16 (всего у книги 36 страниц)
Благодаря наследованию в классе ColorTriangle можно использовать ранее определенные классы Triangle и TwoDShape, дополняя их лишь данными, необходимыми для конкретного применения класса ColorTriangle. Таким образом, наследование способствует повторному использованию кода.
Данный пример демонстрирует еще одну важную деталь: оператор super () всегда обращается к конструктору ближайшего суперкласса. Иными словами, оператор super () в классе ColorTriangle означает вызов конструктора класса Triangle, а в классе Triangle – вызов конструктора класса TwoDShape. Если в иерархии классов для конструктора суперкласса предусмотрены параметры, то все суперклассы должны передавать их вверх по иерархической структуре. Это правило действует независимого от того, нужны ли параметры самому подклассу или не нужны. Порядок вызова конструкторов
В связи с изложенным выше в отношении наследования и иерархии классов может возникнуть следующий резонный вопрос: когда создается объект подкласса и какой конструктор выполняется первым: тот, что определен в подклассе, или же тот, что определен в суперклассе? Так, если имеется суперкласс А и подкласс В, то вызывается ли конструктор класса А раньше конструктора класса В, или же наоборот? Ответ на этот вопрос состоит в том, что в иерархии классов конструкторы вызываются по порядку выведения классов: от суперкласса к подклассу. Более того, оператор super () должен быть первым в конструкторе подкласса, и поэтому порядок, в котором вызываются конструкторы, остается неизменным, независимо от того, используется ли оператор super () или нет. Если оператор super () отсутствует, то выполняется конструктор каждого суперкласса по умолчанию (т.е. конструктор без параметров). В следующем примере программы демонстрируется порядок вызова конструкторов: // Демонстрация порядка вызова конструкторов. // создать суперкласс class А { А() { System.out.println("Constructing A.") ; } } // создать подкласс путем расширения класса А class В extends А { ВО { System.out.println("Constructing В."); } } // создать подкласс путем расширения класса В class С extends В { СО { System.out.println("Constructing С.") ; } } class OrderOfConstruction { public static void main(String args[]) { С с = new С(); } }
Ниже приведен результат выполнения данной программы. Constructing А. Constructing В. Constructing С.
Как видите, конструкторы вызываются по порядку выведения их классов.
По зрелом размышлении можно прийти к выводу, что вызов конструкторов по порядку выведения их классов имеет определенный смысл. Ведь суперклассу ничего не известно ни об одном из производных от него подклассов, и поэтому любая инициализация, которая требуется его членам, осуществляется совершенно отдельно от инициализации членов подкласса, а возможно, это и необходимое условие. Следовательно, онадолжна выполняться первой. Ссылки на суперкласс и объекты подклассов
Как вам должно быть уже известно, Java является строго типизированным языком программирования. Помимо стандартных преобразований и автоматического продвижения простых типов данных, в этом языке строго соблюдается принцип совместимости типов. Это означает, что переменная ссылки на объект класса одного типа, как правило, не может ссылаться на объект класса другого типа. В качестве примера рассмотрим следующую простую программу: // Этот код не подлежит компиляции, class X { int а; X(int i) { а = i; } } class Y { int a; Y(int i) { a = i; } } class IncompatibleRef { public static void main(String args[]) { X x = new X(10); X x2; Y у = new Y(5); x2 = x; // Допустимо, так как обе переменные одного типа. х2 = у; // Ошибка, так как переменные разных типов. } }
Несмотря на то что классы X и Y содержат одинаковые члены, переменной типа X нельзя присвоить ссылку на объект типа Y, поскольку типы объектов отличаются. Вообще говоря, переменная ссылки на объект может указывать только на объекты своего типа.
Но из этого строгого правила соблюдения типов имеется следующее важное исключение: переменной ссылки на объект суперкласса может быть присвоена ссылка на объект любого производного от него подкласса. Следовательно, по ссылке на объект суперкласса можно обращаться к объекту подкласса. Ниже приведен соответствующий пример. // Обращение к объекту подкласса по ссылке на объект суперкласса, class X { int а; X(int i) { а = i; } } class Y extends X { int b; Y(int i, int j) { super(j) ; b = i; } } class SupSubRef { public static void main(String args[]) { X x = new X(10); X x2; Y у = new Y(5, 6) ; x2 = x; // Допустимо, так как обе переменные одного типа. System.out.println("х2.а: " + х2.а); // Класс Y является подклассом X, поэтому переменные х2 и у 1 // могут ссылаться на один и тот же объект производного класса. х2 = у; // По-прежнему допустимо по указанной выше причине. System.out.println("х2.а: " + х2.а); // В классе X известны только члены класса X. х2.а = 19; // Допустимо. //х2.b=27; // Ошибка, так как переменная Ь не является членом класса X. } }
В данном примере класс Y является подклассом X. Следовательно, переменной х2 можно присвоить ссылку на объект типа Y.
Следует особо подчеркнуть, что доступ к конкретным членам класса определяется типом переменной ссылки на объект, а не типом объекта, на который она ссылается. Это означает, что если ссылка на объект подкласса присваивается переменной ссылки на объект суперкласса, то доступ разрешается только к тем частям этого объекта, которые определяются суперклассом. Именно поэтому переменной х2 недоступен член b класса Y, когда она ссылается на объект этого класса. И в этом есть своя логика, поскольку суперклассу ничего не известно о тех членах, которые добавлены в производный от него подкласс. Именно поэтому последняя строка кода в приведенном выше примере была закомментирована.
Несмотря на кажущийся несколько отвлеченным характер приведенных выше рассуждений, им можно найти ряд важных применений на практике. Одно из них рассматривается ниже, а другое – далее в этой главе, когда речь пойдет о переопределении методов.
Один из самых важных моментов для присваивания ссылок на объекты подкласса переменным суперкласса наступает тогда, когда конструкторы вызываются в иерархии классов. Как вам должно быть уже известно, в классе нередко определяется конструктор, принимающий объект своего класса в качестве параметра. Благодаря этому в классе может быть сконструирована копия его объекта. Этой особенностью можно выгодно воспользоваться в подклассах, производных от такого класса. В качестве примера рассмотрим очередные версии классов TwoDShape и Triangle. В оба класса добавлены конструкторы, принимающие объект своего класса в качестве параметра. class TwoDShape { private double width; private double height; // Конструктор по умолчанию. TwoDShape() { width = height = 0.0; } // Параметризированный конструктор. TwoDShape(double w, double h) { width = w; height = h; } // построить объект с одинаковыми значениями // переменных экземпляра width и height TwoDShape(double х) { width = height = x; } // Построение одного объекта на основании другого объекта. TwoDShape(TwoDShape ob) { width = ob.width; height = ob.height; } // Методы доступа к переменным width и height, double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } void showDim() { System.out.println("Width and height are " + width + " and " + height); } } // Подкласс класса TwoDShape для треугольников, class Triangle extends TwoDShape { private String style; // Конструктор по умолчанию. Triangle() { super(); style = "null"; } // Конструктор класса Triangle. Triangle(String s, double w, double h) { super(w, h); // вызвать конструктор суперкласса style = s; } // Конструктор с одним аргументом для построения треугольника. Triangle(double х) { super(х); // вызвать конструктор суперкласса style = "isosceles"; } // построить один объект на основании другого объекта Triangle(Triangle ob) { // Передача ссылки на объект Triangle конструктору класса TwoDShape. super(ob); style = ob.style; } double area() { return getWidth() * getHeight() / 2; } void showStyle() { System.out.println("Triangle is " + style); } } class Shapes7 { public static void main(String args[]) { Triangle tl = new Triangle("right", 8.0, 12.0); // создать копию объекта tl Triangle t2 = new Triangle(tl); System.out.println("Info for tl: "); tl.showStyle(); tl.showDim(); System.out.println ("Area is " + tl.areaO); System.out.println() ; System.out.println("Info for t2: "); t2.showStyle(); t2.showDim(); System.out.println("Area is " + t2.area()); } }
В приведенном выше примере программы объект t2 конструируется на основании объекта tl, и поэтому они идентичны. Результат выполнения данной программы выглядит следующим образом: Info for tl: Triangle is right Width and height are 8.0 and 12.0 Area is 48.0 Info for t2: Triangle is right Width and height are 8.0 and 12.0 Area is 48.0
Обратите внимание на конструктор класса Triangle, код которого приведен ниже. // построить один объект на основании другого объекта Triangle(Triangle ob) { // Передача ссылки на объект Triangle конструктору класса TwoDShape. super(ob); style = ob.style; }
В качестве параметра данному конструктору передается объект Triangle, который затем с помощью вызова super () передается конструктору TwoDShape, как показано ниже. // Построение одного объекта на основании другого объекта. TwoDShape(TwoDShape ob) { width = ob.width; height = ob.height; }
Следует заметить, что конструктор TwoDshape () должен получить объект типа TwoDShape, но конструктор Triangle () передает ему объект типа Triangle. Тем не менее никаких недоразумений не возникает. Ведь, как пояснялось ранее, переменная ссылки на суперкласс может ссылаться на объект производного от него подкласса. Следовательно, конструктору TwoDShape () можно передать ссылку на экземпляр подкласса, производного от класса TwoDShape. Конструктор TwoDShape () инициализирует лишь те части передаваемого ему объекта подкласса, которые являются членами класса TwoDShape, и поэтому не имеет значения, содержит ли этот объект дополнительные члены, добавленные в производных подклассах. Переопределение методов
В иерархии классов часто присутствуют методы с одинаковой сигнатурой и одинаковым возвращаемым значением как в суперклассе, так и в подклассе. В этом случае говорят, что метод суперкласса переопределяется в подклассе. Если переопределяемый метод вызывается из подкласса, то всегда выбирается тот вариант метода, который определен в подклассе. А вариант метода, определенный в суперклассе, скрывается. Рассмотрим в качестве примера следующий фрагмент кода: // Переопределение метода, class А { int i, j; A(int a, int b) { i = a; j = b; } // отобразить переменные i и j void show() { System.out.println("i and j: " + i + " " + j) ; } } class В extends A { int k; В(int a, int b, int c) { super(a, b); k = c; } // Отображение переменной к. Данный метод переопределяет // метод show() из класса А. void show() { System.out.println("k: " + к); } } class Override { public static void main(String args[]) { В subOb = new B(l, 2, 3) ; subOb.show(); // вызвать метод show() из класса В } }
Выполнение этого фрагмента кода дает следующий результат: к: 3
Когда метод show () вызывается для объекта типа В, выбирается вариант этого метода, определенный в классе В. Таким образом, вариант метода show () в классе В переопределяет вариант одноименного метода, объявленный в классе А.
Если требуется обратиться к исходному варианту переопределяемого метода, т.е. тому, который определен в суперклассе, следует воспользоваться ключевым словом super. Например, в приведенном ниже варианте класса В из метода show () вызывается вариант того же метода, определенный в суперклассе. При этом отображаются все переменные экземпляра. class В extends А { int к; В(int a, int b, int с) { super (а, Ь); к = с; } void show() { // Использование ключевого слова super для // вызова метода show(), определенного в классе А. super.show(); System.out.println("k: " + k); } }
Если подставить новый вариант метода show () в предыдущую версию рассматриваемого здесь фрагмента кода, результат его выполнения изменится и будет иметь следующий вид: i and j: 1 2 k: 3
В данном случае super. show () – это вызов метода show (), определенного в суперклассе.
Переопределение метода происходит только в том случае, если сигнатуры переопределяемого и переопределяющего методов совпадают. В противном случае происходит обычная перегрузка методов. Рассмотрим следующую видоизмененную версию предыдущего примера: /* Методы с разными сигнатурами не переопределяются, а перегружаются. */ class А { int i, j; A(int a, int b) { i = a; j = b; } // отобразить переменные i и j void show() { System.out.println("i and j: " + i + " " + j) ; } } // создать подкласс путем расширения класса Л class В extends А { int к; В (int a, int b, int с) { super(а, Ь); к = с; } // Сигнатуры данного метода и метода show() из класса А отличаются, // поэтому вместо переопределения происходит перегрузка метода. void show(String msg) { System.out.println(msg + k); } } class Overload { public static void main(String args[]) { В subOb = new В(1, 2, 3); subOb.show("This is k: "); // вызывается метод show() из класса В subOb.show(); // вызывается метод show() из класса А } }
Выполнение этого фрагмента кода дает следующий результат: This is k: 3 i and j: 1 2
На этот раз в варианте метода show () из класса В предусмотрен строковый параметр. И благодаря этому сигнатура данного метода отличается от сигнатуры метода show () из класса А, для которого параметры не предусмотрены. В результате переопределение метода не происходит. Поддержка полиморфизма
в переопределяемых методах Примеры из предыдущего раздела демонстрируют переопределение методов, но по ним трудно судить, насколько богатые возможности предоставляет этот механизм. В самом деле, если переопределение методов используется только для соблюдения соглашений о пространствах имен, то его можно рассматривать как любопытную, но почти бесполезную особенность языка программирования. Но это совсем не так. Переопределение методов лежит в основе одного из наиболее эффективных языковых средств Java: динамической диспетчеризации методов, представляющей собой механизм вызова переопределяемых методов, когда выбор конкретного метода осуществляется не на этапе компиляции, а в процессе выполнения программы. Динамическая диспетчеризация методов имеет очень большое значение, поскольку именно с ее помощью принцип полиморфизма реализуется на стадии выполнения программ на Java.
Прежде всего напомним один очень важный принцип: переменная ссылки на суперкласс может ссылаться на объект подкласса. В Java этот принцип используется для вызова переопределяемых методов во время выполнения. Если переопределяемый метод вызывается по ссылке на суперкласс, то исполняющая система Java определяет по типу объекта, какой именно вариант метода следует вызвать, причем делает это во время выполнения программы. Если ссылки указывают на разные типы объектов, то и вызываться будут разные версии переопределенных методов. Иными словами, вариант переопределенного метода для вызова определяет не тип переменной, а тип объекта, на который она ссылается. Так, если суперкласс содержит метод, переопределяемый в подклассе, то вызывается метод, соответствующий тому типу объекта, на который указывает переменная ссылки на суперкласс.
Ниже приведен простой пример, демонстрирующий динамическую диспетчеризацию методов в действии. // Демонстрация динамической диспетчеризации методов. class Sup { void who() { System.out.println("who() in Sup"); } } class Subl extends Sup { void who() { System.out.println("who() in Subl"); } } class Sub2 extends Sup { void who() { System.out.println("who() in Sub2"); } } class DynDispDemo { public static void main(String args[]) { Sup superOb = new Sup(); Subl subObl = new Subl(); Sub2 sub0b2 = new Sub2(); Sup supRef; // В каждом из приведенных ниже вызовов конкретный вариант // метода who () выбирается во время выполнения по типу // объекта, на который делается ссылка. supRef = superOb; supRef.who(); supRef = subObl; supRef.who(); supRef = sub0b2; supRef.who(); } }
Результат выполнения данной программы выглядит следующим образом: who() in Sup who () in Subl who() in Sub2
В данном примере программы определяются суперкласс Sup и два его подкласса Subl и Sub2. В классе Sup объявляется метод who (), переопределяемый в его подклассах. В методе main () создаются объекты типа Sup, Subl и Sub2. Там же объявляется переменная supRef ссылки на объект типа Sup. Затем переменной supRef в методе main () поочередно присваиваются ссылки на объекты разного типа, и далее эти ссылки используются для вызова метода who (). Как следует из результата выполнения данной программы, вызываемый вариант метода who () определяется типом объекта, на который ссылается переменная supRef в момент вызова, а не типом самой этой переменной. Причины для переопределения методов
Как упоминалось выше, переопределяемые методы обеспечивают соблюдение принципа полиморфизма при выполнении программ на Java. Полиморфизм в объектно-ориентированных программах имеет большое значение потому, что благодаря ему появляется возможность объявить в суперклассе методы, общие для всех его подклассов, а в самих подклассах – определить специфические реализации всех этих методов или некоторых из них. Переопределение методов – один из способов, которыми в Java реализуется принцип полиморфизма “один интерфейс – множество методов”.
Залогом успешного применения полиморфизма является, в частности, понимание того, что суперклассы и подклассы образуют иерархию в направлении от меньшей специализации к большей. Если суперкласс организован правильно, он предоставляет своему подклассу все элементы, которыми тот может пользоваться непосредственно. В нем также определяются те методы, которые должны быть по-своему реализованы в порожденных классах. Таким образом, подклассы получают достаточную свободу определять собственные методы, реализуя в то же время согласованный интерфейс. Сочетая наследование с переопределением методов, в суперклассе можно определить общую форму методов для использования во всех его подклассах. Демонстрация механизма переопределения методов на примере класса TwoDShape Для того чтобы стало понятнее, насколько эффективным является механизм переопределения методов, продемонстрируем его на примере класса TwoDShape. В приведенных ранее примерах в каждом классе, порожденном от класса TwoDShape, определялся метод area (). Теперь мы знаем, что в этом случае имеет смысл включить метод area () в состав класса TwoDShape и позволить каждому его подклассу переопределить этот метод: в частности, реализовать вычисление площади в зависимости от конкретного типа геометрической фигуры. Именно такой подход и воплощен в приведенном ниже примере программы. Для удобства в класс TwoDShape добавлено поле name, упрощающее написание демонстрационных программ. // Применение динамической диспетчеризации методов. class TwoDShape { private double width; private double height; private String name; // Конструктор по умолчанию. TwoDShape() { width = height = 0.0; name = «null»; } // Параметризированный конструктор. TwoDShape(double w, double h, String n) { width = w; height = h; name = n; } // построить объект с одинаковыми значениями // переменных экземпляра width и height TwoDShape(double х, String n) { width = height = x; name = n; } // построить один объект на основании другого объекта TwoDShape(TwoDShape ob) { width = ob.width; height = ob.height; name = ob.name; } // Методы доступа к переменным width и height, double getWidth() { return width; } double getHeightO { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } String getName() { return name; } void showDim() { System.out.println("Width and height are " + width + " and " + height); } // Метод area() определен в классе TwoDShape. double area() { System.out.println(«area() must be overridden»); return 0.0; } } // Подкласс, производный от класса TwoDShape, // для представления треугольников, class Triangle extends TwoDShape { private String style; // Конструктор по умолчанию. Triangle() { super(); style = «null»; } // Конструктор класса Triangle. Triangle(String s, double w, double h) { super(w, h, «triangle»); style = s; } // Конструктор с одним аргументом для построения треугольника Triangle(double х) { super(х, «triangle»); // вызвать конструктор суперкласса style = «isosceles»; } // построить один объект на основании другого объекта Triangle(Triangle ob) { super(ob); // передать объект конструктору класса TwoDShape style = ob.style; } // Переопределение метода area() для класса Triangle. double area() { return getWidth() * getHeight() / 2; } void showStyle() { System.out.println("Triangle is " + style); } } // Подкласс, производный от класса TwoDShape, // для представлёния прямоугольников, class Rectangle extends TwoDShape { // Конструктор по умолчанию. Rectangle() { super(); } // Конструктор класса Rectangle. Rectangle(double w, double h) { super(w, h, «rectangle»); // вызвать конструктор суперкласса } // построить квадрат Rectangle(double х) { super(х, «rectangle»); // вызвать конструктор суперкласса } // построить один объект на основании другого объекта Rectangle(Rectangle ob) { super(ob); // передать объект конструктору класса TwoDShape } boolean isSquare() { if (getWidth () == getHeightO) return true; return false; } // Переопределение метода area() для класса Rectangle. double area() { return getWidth () * getHeightO; } } 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 (); } } }
Ниже приведен результат выполнения данной программы. object is triangle Area is 48.0 object is rectangle Area is 100.0 object is rectangle Area is 40.0 object is triangle Area is 24.5 object is generic area() must be overridden Area is 0.0
Рассмотрим код данной программы более подробно. Теперь, как и предполагалось при написании программы, метод area () входит в состав класса TwoDShape и переопределяется в классах Triangle и Rectangle. В классе TwoDShape метод area () играет роль заполнителя и лишь уведомляет пользователя о том, что этот метод должен быть переопределен в подклассе. При каждом переопределении метода area () в нем реализуются средства, необходимые для того типа объекта, который инкапсулируется в подклассе. Так, если требуется реализовать класс для эллипсов, метод area () придется переопределить таким образом, чтобы он вычислял площадь этой фигуры.
У рассматриваемой здесь программы имеется еще одна важная особенность. Обратите внимание на то, что в методе main () геометрические фигуры объявляются как массив объектов типа TwoDShape. Но на самом деле элементами массива являются ссылки на объекты Triangle, Rectangle и TwoDShape. Это вполне допустимо. Ведь, как пояснялось ранее, переменная ссылки на суперкласс может ссылаться на объект его подкласса. В этой программе организован перебор элементов массива в цикле и вывод сведений о каждом объекте. Несмотря на всю простоту данного примера, он наглядно демонстрирует потенциальные возможности как наследования, так и переопределения методов. Тип объекта, на который указывает переменная ссылки на суперкласс, определяется на этапе выполнения программы и обрабатывается соответствующим образом. Если объект является производным от класса TwoDShape, его площадь вычисляется при вызове метода area (). Интерфейс для данной операции оказывается общим и не зависит от того, с какой именно геометрической фигурой приходится иметь дело каждый раз. Применение абстрактных классов
Иногда требуется создать суперкласс, в котором определяется лишь самая общая форма для всех его подклассов, а наполнение ее деталями предоставляется каждому из этих подклассов. В таком классе определяется лишь характер методов, которые должны быть конкретно реализованы в подклассах, а не в самом суперклассе. Подобная ситуация возникает, например, в связи с невозможностью получить содержательную реализацию метода в суперклассе. Именно такая ситуация была продемонстрирована в варианте класса TwoDShape из предыдущего примера, где метод area () был просто определен как заполнитель. Такой метод не вычисляет и не выводит площадь двумерной геометрической формы любого типа.
Создавая собственные библиотеки классов, вы можете сами убедиться в том, что у метода зачастую отсутствует содержательное определение в контексте его суперкласса. Подобное затруднение разрешается двумя способами. Один из них, как показано в предыдущем примере, состоит в том, чтобы просто выдать предупреждающее сообщение. И хотя такой способ может пригодиться в некоторых случаях, например при отладке, в практике программирования он обычно не применяется. Ведь в суперклассе могут быть объявлены методы, которые должны быть переопределены в подклассе, чтобы этот класс стал содержательным. Рассмотрим для примера класс Triangle. Он был бы неполным, если бы в нем не был переопределен метод area (). В подобных случаях требуется какой-то способ, гарантирующий, что в подклассе действительно будут переопределены все необходимые методы. И такой способ в Java имеется. Он состоит в использовании абстрактного метода.
Абстрактный метод создается с помощью указываемого модификатора типа abstract. У абстрактного метода отсутствует тело, и поэтому он не реализуется в суперклассе. Это означает, что он должен быть переопределен в подклассе, поскольку его вариант из суперкласса просто непригоден для использования. Для определения абстрактного метода служит приведенная ниже общая форма, abstract тип имя (список_параметров);
Как видите, у абстрактного метода отсутствует тело. Модификатор abstract может применяться только к обычным методам, но не к статическим методам (static) и конструкторам.
Класс, содержащий один или более абстрактный метод, должен быть также объявлен как абстрактный, для чего перед его объявлением class указывается модификатор abstract. А поскольку реализация абстрактного класса не определяется полностью, то у него не может быть объектов. Следовательно, попытка создать объект абстрактного класса с помощью оператора new приведет к ошибке во время компиляции.
Когда подкласс наследует абстрактный класс, в нем должны быть реализованы все абстрактные методы суперкласса. В противном случае подкласс должен быть также определен как abstract. Таким образом, атрибут abstract наследуется до тех пор, пока не будет достигнута полная реализация класса.
Используя абстрактный класс, мы можем усовершенствовать рассматривавшийся ранее класс TwoDShape-Для неопределенной двумерной геометрической фигуры понятие площади не имеет никакого смысла, поэтому в приведенной ниже версии предыдущей программы метод area () и сам класс TwoDShape объявляются как abstract. Это, конечно, означает, что во всех классах, производных от класса TwoDShape, должен быть переопределен метод area (). // Создание абстрактного класса. // Теперь класс TwoDShape является абстрактным. abstract class TwoDShape { private double width; private double height; private String name; // Конструктор no умолчанию. TwoDShape() { width = height = 0.0; name = "null"; } // Параметризированный конструктор. TwoDShape(double w, double h, String n) { width = w; height = h; name = n; } // построить объект с одинаковыми значениями // переменных экземпляра width и height TwoDShape(double х, String n) { width = height = x; name = n; } // построить один объект на основании другого объекта TwoDShape(TwoDShape ob) { width = ob.width; height = ob.height; name = ob.name; } // Методы доступа к переменным width и height, double getWidth() { return width; } double getHeightO { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } String getName() { return name; } void showDim() { System.out.println("Width and height are " + width + " and " + height); } // Теперь метод area () является абстрактным. abstract double area(); } // Подкласс, производный от класса TwoDShape, // для представления треугольников, class Triangle extends TwoDShape { private String style; // Конструктор по умолчанию. Triangle() { super () ; style = "null"; } // Конструктор класса Triangle. Triangle(String s, double w, double h) { super(w, h, "triangle"); style = s; } // Конструктор с одним аргументом для построения треугольника. Triangle(double х) { super(х, "triangle"); // вызвать конструктор суперкласса style = "isosceles"; } // построить один объект на основании другого объекта Triangle(Triangle ob) { super(ob); // передать объект конструктору класса TwoDShape style = ob.style; } double area() { return getWidth() * getHeight() / 2; } void showStyle() { System.out.println("Triangle is " + style); } } // Подкласс, производный от класса TwoDShape, // для представления прямоугольников. class Rectangle extends TwoDShape { // Конструктор по умолчанию. Rectangle() { super(); } // Конструктор класса Rectangle. Rectangle(double w, double h) { super(w, h, "rectangle"); // вызвать конструктор суперкласса } // построить квадрат Rectangle(double х) { super(х, "rectangle"); // вызвать конструктор суперкласса } // построить один объект на основании другого объекта Rectangle(Rectangle ob) { super(ob); // передать объект конструктору класса TwoDShape } boolean isSquare() { if (getWidth () == getHeightO) return true; return false; } double area() { return getWidth () * getHeightO; } } class AbsShape { public static void main(String args[]) { TwoDShape shapes[] = new TwoDShape[4]; 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); for(int i=0; i < shapes.length; i++) { System.out.println("object is " + shapes[i].getName()); System.out.println("Area is " + shapes[i].area()); System.out.println(); } } }