Java: руководство для начинающих (ЛП) - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
В приведенном выше примере программы объект t2 конструируется на основании объекта tl, и поэтому они идентичны. Результат выполнения данной программы выглядит следующим образом:Info for tl:Triangle is rightWidth and height are 8.0 and 12.0Area is 48.0Info for t2:Triangle is rightWidth and height are 8.0 and 12.0Area 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 2k: 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: 3i 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 Supwho () in Sublwho() in Sub2
В данном примере программы определяются суперкласс Sup и два его подкласса Subl и Sub2. В классе Sup объявляется метод who (), переопределяемый в его подклассах. В методе main () создаются объекты типа Sup, Subl и Sub2. Там же объявляется переменная supRef ссылки на объект типа Sup. Затем переменной supRef в методе main () поочередно присваиваются ссылки на объекты разного типа, и далее эти ссылки используются для вызова метода who (). Как следует из результата выполнения данной программы, вызываемый вариант метода who () определяется типом объекта, на который ссылается переменная supRef в момент вызова, а не типом самой этой переменной.Причины для переопределения методов