Ниже приведен результат выполнения данной программы. 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 tclass="underline" "); 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 tclass="underline" 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() из класса В } }