Java. Полное руководство. 8-е издание. С. Н. Тригуб Перевод с английского и редакция
Скачать 25.04 Mb.
|
ГЛАВА Наследование Одним из фундаментальных понятий объектно-ориентированного программирования является наследование, поскольку оно позволяет создавать иерархические классификации. Используя наследование, можно создать общий класс, который определяет характеристики, общие для набора связанных элементов. Затем этот класс может наследоваться другими, более специализированными, классами, каждый из которых будет добавлять свои уникальные характеристики. В терминологии Java наследуемый класс называют суперклассом. Наследующий класс носит название подкласса Следовательно, подкласс — это специализированная версия суперкласса. Он наследует все члены, определенные суперклассом, и добавляет собственные, уникальные, элементы. Основы наследования Чтобы наследовать класс, достаточно просто вставить определение одного класса в другой с использованием ключевого слова e x te n d s . В качестве иллюстрации рассмотрим короткий пример. Следующая программа создает суперкласс Аи подкласс В. Обратите внимание на использование ключевого слова e x te n d s для создания подкласса класса А Простой пример наследования Создание суперкласса, class Аи Создание подкласса за счет расширения класса А class В extends А { int к showk() к " + к s u m () { System.out.println("i+j+k: " + (i+j+k)); } } 1 9 6 Часть I. Язык Java class Simplelnheritance { public static void main(String a r g s []) { A superOb = new A (В subOb = new В (); // Суперкласс может использоваться самостоятельно superOb.i = 10; superOb.j = 2 Содержимое superOb: "); superOb.showij(); System.out.println(); /* Подкласс имеет доступ ко всем открытым членам своего суперкласса. */ subOb.i = 7; subOb.j = 8; subOb.k = Содержимое subOb: "); subOb.showij(); Сумма i, j ив (Эта программа создает следующий вывод. Содержимое superOb: i и j : 10 20 Содержимое subOb: i и j : 7 8 k: Сумма i , j ив Как видите, подкласс В включает в себя все члены своего суперкласса А. Именно поэтому объект subOb имеет доступ к переменными и может вызывать метод showi j (). Кроме того, внутри метода sum () возможна непосредственная ссылка на переменные i и j , как если бы они были частью класса В. Несмотря на то что А — суперкласс класса Вон также является полностью независимым, самостоятельным, классом. То, что класс является суперклассом подкласса, не означает невозможность его самостоятельного использования. Более того, подкласс может быть суперклассом другого подкласса. Общая форма объявления класса, который наследуется от суперкласса, следующая имя_подкласса extends имя_суперкласса { // тело класса } Для каждого создаваемого подкласса можно указывать только один суперкласс. Язык Java не поддерживает наследование нескольких суперклассов водном подклассе. Как было сказано, можно создать иерархию наследования, в которой подкласс становится суперклассом другого подкласса. Однако никакой класс не может быть собственным суперклассом. Глава 8. Наследование 1 9 Доступ к членами наследование Хотя подкласс включает в себя все члены своего суперкласса, он не может получать доступ к тем членам суперкласса, которые объявлены как p r i v a t e . Например, рассмотрим следующую простую иерархию классов В иерархии классов закрытые члены остаются закрытыми для своего класса. Эта программа содержит ошибку, и ее компиляция будет невозможна Создание суперкласса, class А { int i; // открытая по умолчанию int j; // закрытая для Ах уху Переменная j класса А в этом классе недоступна class В extends А { int total; void s u m () { total = i + j; // ОШИБКА, j в этом классе недоступна Access { public static void main(String a r g s []) В subOb = new В (); subOb.setij(10, 12); subOb.s u m (Сумма равна " + Компиляция этой программы будет невозможна, поскольку использование переменной j внутри метода sum () класса В приводит к нарушению правил доступа. Поскольку переменная j объявлена как p r i v a t e , она доступна только другим членам ее собственного класса. Подкласс не имеет к ней доступа. Помните! Член класса, который объявлен как закрытый, останется закрытым для своего класса Он недоступен любому коду за пределами его класса, в том числе подклассам. Более реальный пример Рассмотрим более реальный пример, который поможет проиллюстрировать возможности наследования. В нем мы расширим последнюю версию класса Box, разработанную в предыдущей главе, добавив в нее четвертый компонент, который 1 9 Часть I. Язык назовем w e ig h t (вес. Таким образом, новый класс будет содержать ширину, высоту, глубину и вес параллелепипеда В этой программе наследование используется для расширения класса Box. class Box { double width; double height; double depth; // конструирование клона объекта Box(Box ob) { // передача объекта конструктору width = ob.width; height = ob.height; depth = ob.depth; } // конструктор, используемый при указании всех измерений Box(double w, double h, double d) { width = w; height = h; depth = d; } // конструктор, используемый, если изменений нет B o x () { width = -1; // значение -1 используется для указания = -1; // неинициализированного depth = -1; // параллелепипеда конструктор, используемый при создании куба Box(double len) { width = height = depth = len; } // вычисление и возврат объема double vo l u m e () { return width * height * depth; } } // Расширение класса Box включением в него веса class BoxWeight extends Box { double weight; // вес параллелепипеда конструктор BoxWeight BoxWeight(double w, double h, double d, double m) { width = w; height = h; depth = d; weight = m; } } class DemoBoxWeight { public static void main(String a r g s []) { BoxWeight myboxl = new BoxWeight(10, 20, 15, 34.3); BoxWeight mybox2 = new BoxWeight(2, 3, 4, 0.076); double vol; vol = my b o x l .v o l u m e (Объем myboxl равен " + vol); Глава 8. Наследование 1 9 Вес myboxl равен " + myboxl.weight); System.out.println(); vol = Объем mybox2 равен " + vol); System.out.println("Bee mybox2 равен " + Эта программа создает следующий вывод. Объем myboxl равен 3 000.0 Вес myboxl равен 34.3 Объем mybox2 равен 2 4.0 Вес mybox2 равен 0.07 Класс BoxWeight наследует все характеристики класса Box и добавляет к ним компонент weight. Классу BoxWeight ненужно воссоздавать все характеристики класса Box. Он может просто расширять класс Box в соответствии с конкретными целями. Основное преимущество наследования состоит в том, что как только суперкласс, который определяет общие атрибуты набора объектов, создан, его можно использовать для создания любого количества более специализированных классов. Каждый подкласс может точно определять свою собственную классификацию. Например, следующий класс наследует характеристики класса Box и добавляет атрибут цвета / Этот код расширяет класс Box, включая в него атрибут цвета class ColorBox extends Box { int color; // цвет параллелепипеда w, double h, double d, int c) { width = w; height = h; depth = d; color = Помните, как только суперкласс, который определяет общие аспекты объекта, создан, он может наследоваться для создания специализированных классов. Каждый подкласс добавляет собственные уникальные атрибуты. В этом заключается сущность наследования. Переменная суперкласса может ссылаться на объект подкласса Ссылочной переменной суперкласса может быть присвоена ссылка на любой подкласс, производный отданного суперкласса. Этот аспект наследования будет весьма полезен во множестве ситуаций. Рассмотрим следующий пример RefDemo { public static void main(String a r g s []) { BoxWeight weightbox = new BoxWeight(3, 5, 7, 8.37); Box plainbox = new B o x (); double vol; vol = Объем weightbox равен " + vol); System.out.println("Bee weightbox равен " + weightbox.weight); System.out.println(); 200 Часть I. Язык Java // присваивание ссылке на объект BoxWeight ссылки на объект Box plainbox = weightbox; vol = plainbox.volume(); // OK, метод vo l u m e () определен в Box Объем plainbox равен " + vol); /* Следующий оператор ошибочен, поскольку plainbox не определяет член weight. */ // Вес plainbox равен " + В этом примере weightbox — ссылка на объекты класса BoxWeight, a pain- box — ссылка на объекты класса Box. Поскольку BoxWeight — подкласс класса Box, ссылке painbox можно присваивать ссылку на объект Важно понимать, что доступные объекты определяются типом ссылочной переменной, а не типом объекта, на который она ссылается. То есть при присваивании ссылочной переменной суперкласса ссылки на объект подкласса доступ предоставляется только к указанным в ней частям объекта, определенного суперклас сом. Именно поэтому объект plainbox не имеет доступа к переменной weight даже в том случае, когда он ссылается на объект класса BoxWeight. Если немного подумать, это становится понятным — суперклассу неизвестно, что именно подкласс добавляет в него. Поэтому последняя строка кода в предыдущем фрагменте оформлена в виде комментария. Ссылка объекта класса Box не имеет доступа к полю weight, поскольку оно не определено в классе Хотя все сказанное выше может казаться несколько запутанным, эти особенности находят ряд практических применений, два из которых рассматриваются в последующих разделах данной главы. Использование ключевого слова В предшествующих примерах классы, производные от класса Box, были реализованы не столь эффективно и надежно, как могли бы. Например, конструктор BoxWeight () явно инициализирует поля width, height и depth класса Box. Это не только ведет к дублированию кода суперкласса, что весьма неэффективно, но и предполагает наличие у подкласса доступа к этим членам. Однако в ряде случаев придется создавать суперкласс, подробности реализации которого доступны только для него самого (тес закрытыми членами данных. В этом случае подкласс никак не может самостоятельно непосредственно обращаться к этим переменным или инициализировать их. Поскольку инкапсуляция — один из главных атрибутов ООП, неудивительно, что язык Java предлагает решение этой проблемы. Во всех случаях, когда подклассу нужно сослаться на его непосредственный суперкласс, это можно сделать при помощи ключевого слова Ключевое слово super имеет две общие формы. Первую используют для вызова конструктора суперкласса, а вторую — для обращения к члену суперкласса, скрытому членом подкласса. Рассмотрим обе формы. Использование ключевого слова s u p e r для вызова конструкторов суперкласса Подкласс может вызывать конструктор, определенный его суперклассом, с помощью следующей формы ключевого слова super. Глава 8. Наследование 2 0 1 super ( список_аргументов) Здесь список аргументов определяет любые аргументы, требуемые конструктору в суперклассе. Вызов метода super () всегда должен быть первым оператором, выполняемым внутри конструктора подкласса. В качестве иллюстрации использования метода super () рассмотрим следующую усовершенствованную версию класса BoxWeight. // Теперь класс BoxWeight использует ключевое слово super // для инициализации своих атрибутов объекта Box. class BoxWeight extends Box { double weight; // вес параллелепипеда инициализация переменных width, height и depth с помощью sup e r () BoxWeight(double w, double h, double d, double m) { super(w, h, d ) ; // вызов конструктора суперкласса weight = В этом примере метод BoxWeight () вызывает метод super () с аргументами w, h и d. Это приводит к вызову конструктора Box () , который инициализирует переменные width, height и depth, используя переданные ему значения соответствующих параметров. Теперь класс BoxWeight не инициализирует эти значения самостоятельно. Ему нужно инициализировать только свое уникальное значение — weight. В результате при необходимости эти значения могут оставаться закрытыми значениями класса В приведенном примере метод super () был вызван стремя аргументами. Поскольку конструкторы могут быть перегруженными, метод super () можно вызывать, используя любую форму, определенную суперклассом. Программа выполнит тот конструктор, который соответствует указанным аргументам. В качестве примера приведем полную реализацию класса BoxWeight, которая предоставляет конструкторы для различных способов создания параллелепипедов. В каждом случае метод super () вызывается с соответствующими аргументами. Обратите внимание на то, что внутри класса Box его члены width, height и depth объявлены как закрытые Полная реализация класса BoxWeight. class Box { private double width; private double height; private double depth; // конструирование клона объекта Box(Box ob) { // передача объекта конструктору width = ob.width; height = ob.height; depth = ob.depth; } // конструктор, используемый при указании всех измерений Box(double w, double h, double d) { width = w; height = h; depth = d; } // конструктор, используемый, если ни одно из измерений не указано B o x () { width = -1; // значение -1 используется для указания Часть I. Язык Java height = -1; 11 неинициализированного depth = -1; // параллелепипеда конструктор, используемый при создании куба len) { width = height = depth = len; } // вычисление и возврат объема vo l u m e () { return width * height * depth; } // Теперь BoxWeight полностью реализует все конструкторы class BoxWeight extends Box { double weight; // вес параллелепипеда конструирование клона объекта ob) { // передача объекта конструктору super(ob); weight = ob.weight; } // конструктор, используемый при указании всех параметров BoxWeight(double w, double h, double d, double m) { super(w, h, d ) ; // вызов конструктора суперкласса weight = m; } // конструктор, используемый по умолчанию BoxWeight() { s uper(); weight = -1; } // конструктор, используемый при создании куба BoxWeight(double len, double m) { super(len); weight = m; } } class DemoSuper { public static void main(String a r g s []) { BoxWeight myboxl = new BoxWeight(10, 20, 15, 34.3); BoxWeight mybox2 = new BoxWeight(2, 3, 4, 0.076); BoxWeight mybox3 = new BoxWeight(); / / по умолчанию mycube = new BoxWeight(3, 2); BoxWeight myclone = new BoxWeight(myboxl); double vol; vol = Объем myboxl равен " + vol); System.out.println("Bee myboxl равен " + m y b o x l .weight); System.out.println(); vol = Объем mybox2 равен " + vol); System.out.println("Bee of mybox2 равен " + mybox2.weight) System.out.println(); Глава 8. Наследование 2 0 3 vol = шу Ь ох З Объем туЬохЗ равен " + Вес туЬохЗ равен " + ту ЬохЗ.weight); System.out.println(); vol = myclone.vo l u m e (Объем myclone равен " + vol); System, out .println ("Bee myclone равен 1 1 + myclone .weight) ; System.out.println(); vol = my c u b e .vo l u m e (Объем mycube равен " + vol); System.out.println("Bee mycube равен " + mycube.weight); . Эта программа создает следующий вывод. Объем myboxl равен 3 000.0 Вес myboxl равен 34.3 Объем mybox2 равен 24.0 Вес mybox2 равен 0.07 6 Объем туЬохЗ равен -1.0 Вес туЬохЗ равен -1.0 Объем myclone равен 3000.0 Вес myclone равен 3 4.3 Объем mycube равен 27.0 Вес mycube равен Обратите особое внимание наследующий конструктор в классе BoxWeight. // конструирование клона объекта ob) { // передача объекта конструктору super(ob); weight = Обратите внимание на то, что метод super () выполняет передачу объекту класса BoxWeight, а не класса Box. Тем не менее это все равно ведет к вызову конструктора Box (Box. ob) . Как уже было отмечено, переменную суперкласса можно использовать для ссылки на любой объект, унаследованный от этого класса. Таким образом, объект класса BoxWeight можно передать конструктору Box (). Конечно, классу Box будут известны только его собственные члены. Рассмотрим основные концепции применения метода super (). Когда подкласс вызывает метод super () , он вызывает конструктор своего непосредственного су перкласса. Таким образом, метод super () всегда ссылается на суперкласс, расположенный в иерархии непосредственно над вызывающим классом. Это справедливо даже в случае многоуровневой иерархии. Кроме того, метод super () всегда должен быть первым оператором, выполняемым внутри конструктора подкласса. Второе применение ключевого слова s u p e Вторая форма ключевого слова super действует подобно ключевому слову this, за исключением того, что всегда ссылается на суперкласс подкласса, в котором она использована. Общая форма этого применения ключевого слова super имеет следующий вид. член Здесь член может быть методом либо переменной экземпляра |