Главная » Java, Структуры данных и алгоритмы » Использование наследования классов в Java

0

Существуют два вида наследования классов в Java — конкретизация (сужение) и расширение. При конкретизации общий класс уточняется в частных подклассах. Эти подклассы по отношению к суперклассу являются «экземпляром» и наследуют все методы суперкласса. Если метод выполняется корректно в подклассе, никаких дальнейших изменений не требуется. С другой стороны, если в подклассе метод суперкласса не работает, то в этом подклассе данный метод следует переопределить, чтобы он мог выполнять необходимые в этом классе операции. Например, имеется общий класс Dog, который содержит метод drink и метод sniff. При конкретизации этого класса в классе Bloodhound метод drink не потребует изменений, так как все собаки лакают одинаковым образом. Однако, вероятно, понадобится переопределение метода sniff, поскольку собака породы бладхаунд обладает гораздо более развитым нюхом, чем «обобщенная» собака. В данном случае класс Bloodhound конкретизирует метод своего суперкласса Dog.

При использовании расширения благодаря наследованию классов многократно используются участки кода с описанием методов суперкласса, после чего в подклассы добавляются новые методы, расширяя таким образом функциональные возможности класса. Например, в том же классе Dog можно создать подкласс BorderCollie, который наследует все общие методы класса Dog, после чего в нем дополнительно описывается метод herd, так как собаки породы колли обладают пастушьим инстинктом, не присущим остальным собакам. При добавлении нового метода расширяются функциональные возможности общего класса.

Наследование классов в Java

В Java каждый класс может расширить только один класс. Если даже в описании класса отсутствует явный оператор extends, этот класс наследует только один класс, который в данном случае является классом java.lang.Object. В связи с этой особенностью говорят, что в Java допускается только единичное наследование классов.

Типы переопределения методов

При объявлении нового класса в Java используются два типа переопределения методов, уточнение и замещение. При замещении переопределенный в подклассе суперкласса метод полностью замещается новым методом (как в случае приведенного выше метода sniff класса Bloodhound) В Java для всех стандартных методов класса используется данный тип переопределения.

При уточнении же новый метод не замещает метод суперкласса, а вместо этого в метод суперкласса дописывается дополнительный код.

В Java все методы конструктора используют уточнения при переопределении методов по схеме сцепления конструкторов. В частности, выполнение конструктора начинается с обращения к конструктору суперкласса, которое может быть явным или неявным. Для явного обращения к конструктору используется оператор super, который указывает на вызов суперкласса (например, super() вызывает конструктор суперкласса без аргументов). Если же в теле конструктора явное обращение отсутствует, компилятор автоматически помещает в первой строке конструктора обращение к методу super(). То есть в языке Java в конструкторах используется уточнение при переопределении методов, а в обычных методах используется замещение.

Ключевое слово this

Иногда в классе Java необходимо обратиться х текущему экземпляру данного класса, который в данный момент обрабатывается методом. Для такого обращения используется ключевое слово this. Применение этой конструкции удобно в случае необходимости обращения к полю текущего объекта, имя которого совпадает с именем переменной, описанной в данном блоке, как показано в следующей программе:

public class ThisTester {

// переменная экземпляра класса: public int dog = 2;

// конструктор ThisTester() {

Л null-конструктор*/ }

public void clobber() { double dog=5.0;

this.dog = (int) dog; // две разные переменные dog! }

public static void main(String args[ ]) { ThisTester t = new ThisTester(); System.out.printlnfThe dog field – " + t.dog); t.clobber();

System.out.printlnC’After clobbering, dog = " + t.dog);

}

}

После выполнения программы выводится следующая информация:

The dog field = 2

After clobbering, dog = 5

Пример наследования классов в Java

Для того чтобы более конкретно изучить понятия наследования и полиморфизма в Java, рассмотрим несколько простых примеров.

В частности, рассмотрим несколько примеров классов, в которых создаются и выводятся числовые прогрессии. Числовая прогрессия — это последовательность значений, причем значение каждого элемента прогрессии зависит от одного или более предыдущих членов. Например, в арифметической прогрессии значение следующего члена прогрессии определяется путем сложения, а в геометрической прогрессии — путем умножения. В любом случае в прогрессии должен существовать способ определения первого члена прогрессии, а также способ определения значения текущего члена прогрессии.

Начнем с описания класса Progression, показанного фрагментом кода 2.1. В этом классе описаны «общие» поля и методы числовой прогрессии. В частности, данный класс описывает следующие два поля типа long-integer:

•    first: значение первого члена прогрессии;

•    cur: значение текущего члена прогрессии;

а также следующие три метода:

firstValue(): определяет первый член профессии и возвращает его значение.

Input: нет; Output: целое число типа \or\g.

nextValue(): переходит к следующему члену прогрессии и возвращает его значение. Input: нет; Output: целое число типа long.

printProgression(n): вычисляет прогрессию и выводит значения первых п членов прогрессии. Input: целое число; Output: нет.

Говоря, что метод printProgression не имеет выходных данных, имеют в виду, что он не возвращает никакого значения, в то время как методы firstValue и nextValue возвращают значения типа long-integer. Таким образом, firstValue и nextValue являются функциями, a printProgression — процедурой.

Класс Progression также содержит метод Progression(), который является конструктором. Такие методы-конструкторы задают первоначальные значения переменных экземпляра класса при создании нового объекта данного класса. Класс Progression является общим суперклассом, который наследуют более низшие классы. Таким образом, указанный конструктор является кодом, который будет включен в коды всех классов, расширяющих класс Progression.

!**

*Класс числовых прогрессий.

7

public class Progression {

/** значение первого члена прогрессии. 7 protected long first;

Г* значение текущего члена прогрессии. 7 protected long cur;

Г* конструктор по умолчанию 7 Progression() { cur = first = 0;

}

Г* определяет первый член прогрессии. *

*                      возвращает значение первого члена прогрессии

7

protected long firstValue() { cur = first; return cur;

}

/** прогрессия переходит к следующему значению.

*                

*                      возвращает значение следующего члена прогрессии

7

protected long nextValue() {

return ++cur; // следующее значение no умолчанию }

Г* печатает значения первых п членов прогрессии.

*                

‘параметр п — число членов прогрессии, выводимых на печать.

7

public void printProgression(int n) { System.out.print(firstValue()); for (int i = 2; i <= n; i++)

System.out.printf " + nextValue());

System, out. println(); // конец строки }

}

Фрагмент кода 2.1. Общий класс числовой прогрессии

Класс арифметической прогрессии

Далее рассмотрим класс ArithPrpgression, который представлен на фрагменте кода 2.2.

Этот класс описывает прогрессию, в которой значение каждого последующего члена определяется путем прибавления к предыдущему значению фиксированного инкремента inc. Другими словами, класс Arith- Progression описывает арифметическую прогрессию.

Класс ArithProgression наследует поля first и cur, а также методы firstValue() и printProgression(n) из класса Progression. В нем дополнительно описано поле inc, которое содержит значение инкремента, а также два конструктора для установки значений инкремента. В этом классе также переопределяется метод nextValue() в соответствии со способом получения следующего значения арифметической прогрессии.

В данном случае используется свойство полиморфизма. Если при ссылке на класс Progression производится обращение к объекту класса ArithProgression, то используются методы класса ArithProgression firstValue() и nextValue(). Полиморфизм проявляется и при выполнении наследованной версии метода printProgression(n), так как в этом случае производятся неявные обращения к методам firstValue() и nextValue() «текущего» объекта (вызываемый в Java оператором this), который является экземпляром класса ArithProgression.

При описании класса ArithProgression добавлены два конструктора: конструктор по умолчанию, не содержащий параметров, и метод, содержащий один целочисленный параметр — инкремент прогрессии. В действительности конструктор по умолчанию просто вызывает с помощью оператора this конструктор с параметрами и передает значение 1 в качестве значения инкремента. Эти два метода демонстрируют перегрузку методов, при которой в одном классе может встречаться множество методов с одинаковыми именами, так как полное описание метода, то есть его сигнатура, содержит имя метода, класс объектов, вызывающий метод, а также тип аргументов, передаваемых в метод. В данном случае происходит перегрузка конструкторов (конструктора по умолчанию и параметрического конструктора).

В первой строке конструктора по умолчанию происходит обращение this(l) к параметрическому конструктору, что создает исключение из общего правила сцепления конструкторов, рассмотренного в п. 2.2.3. В частности, если в первой строке конструктора С с помощью оператора this вызывается конструктор С" из того же класса, это означает, что в данном случае для С’ конструктор суперкласса не вызывается. Следует отметить, что, в конечном счете, конструктор суперкласса все же будет вызван, явно или неявно. В частности, в случае с классом ArithProgression обращение по умолчанию к конструктору суперкласса Progression происходит неявно в первой строке параметрического конструктора ArithProgression.

Более подробно конструкторы рассмотрены в разделе 1.2. Г

‘Арифметическая прогрессия 7

7 class ArithProgression extends Progression {

Г* Инкремент.7 protected long inc;

// наследует переменные first и cur

Г* конструктор по умолчанию устанавливает инкремент = 1. ArithProgression() { this(1);

}

Г параметрический конструктор задает инкремент. 7 ArithProgression(long increment) { inc = increment;

}

Л* получение следующего члена прогрессии путем добавления

*               инкремента к текущему значению.

*                

* возвращает следующее значение прогрессии

7

protected long nextValueQ { cur += inc; return cur;

}

// наследует методы firstValue() и printProgression(int). }

Фрагмент кода 2.2. Класс арифметической прогрессии

Класс геометрической прогрессии

Рассмотрим класс GeomProgression, представленный фрагментом кода 2.3, который создает геометрическую- прогрессию путем умножения предыдущего значения прогрессии на постоянный знаменатель Ь. Геометрическая прогрессия подобна общей прогрессии, за исключением способа получения очередного значения. Таким образом, класс

GeomProgression объявляется подклассом класса Progression. Как и класс ArithProgression, класс GeomProgression наследует поля first и cur, а также методы firstValue() и printProgression из класса Progression.

Г

Теометрическая прогрессия

7

class GeomProgression extends Progression { // наследует переменные first и cur

/** конструктор по умолчанию устанавливает значение знаменателя 2.

7

GeomProgression() { this(2);

}

/** параметрический конструктор задает значение знаменателя. *

*       @ param base — знаменатель прогрессии

7

GeomProgression(long base) { first = base; cur = first;

}

Л* получение следующего члена прогрессии путем умножения

*       текущего значения на знаменатель

*        

* возвращает следующее значение прогрессии

7

protected long nextValue() { cur *= first; return cur;

}

// наследует методы firstValue() и printProgression(int)

}

Фрагмент кода 2.3. Класс геометрической прогрессии

Класс прогрессии Фибоначчи

В качестве еще одного примера рассмотрим класс FibonacciPro- gression, который представляет еще один вид прогрессии — прогрессию

Фибоначчи, в которой каждое следующее значение получается в результате сложения текущего и предыдущего значений. Класс прогрессии Фибоначчи представлен фрагментом кода 2.4. Заметим, что в классе FibonacciProgression применен параметрический конструктор для установки различных начальных значений прогрессии.

Г

‘Прогрессия Фибоначчи

7

7 class FibonacciProgression extends Progression { Г* предыдущее значение. 7 long prev;

// наследует переменные first и cur

Г* конструктор по умолчанию устанавливает начальные значения

*                      прогрессии равными 0 и 1. 7 FibonacciProgressionQ {

thls(0, 1);

}

Л* параметрический конструктор задает значения первого и второго

*       члена прогрессии.

*       param valuel — значение первого члена

*                      param value2 — значение второго члена 7

Fibon&cciProgression(long valuel, long value2) { first = valuel; prev = value2 — valuel;

}

/** вычисление следующего члена прогрессии путем сложения

*       текущего и предыдущего значения прогрессии

*        

*       возвращает следующее значение прогрессии

*        

protected long nextValue() { long temp = prev; prev = cur; cur += temp; return cur;

}

// наследует методы firstValue() и printProgression(int).

}

Фрагмент кода 2.4. Класс прогрессии Фибоначчи

На рис. 2.5 представлена схема наследования, наглядно отображающая происхождение классов различных видов прогрессии из общего класса’ Progression.

Рис. 2.5. Диаграмма наследования классов для класса Progression и его подклассов

В завершение примера опишем класс Tester, представленный фрагментом кода 2.5, который выполняет простое тестирование каждого из трех классов. В этом классе переменная prog является полиморфной в ходе выполнения метода main, поскольку она поочередно обращается к объектам классов ArithProgression, GeomProgression и FibonacciProgression. При обращении исполняющей Java-системы к методу main класса Tester итогом является результат, отображенный фрагментом кода 2.6.

Приведенный пример сравнительно невелик, однако дает наглядное представление о наследовании классов в Java. Класс Progression, его подклассы и программа-тестер содержат ряд недостатков, которые, однако, не вполне рчевидны. Один из них состоит в том, что значения геометрической прогрессии и прогрессии Фибоначчи увеличиваются очень быстро, однако в примере отсутствует обработка неизбежно возникающего переполнения длинных целых чисел. Например, так как З40 > 263, геометрическая прогрессия со знаменателем 3 выйдет за пределы длинных целых чисел после 40 повторений. Кроме того, значение 94-го члена прогрессии Фибоначчи больше 263, что приведет к переполнению после 94 повторений. Еще одна проблема заключается в том, что невозможно произвольно задать начальные значения прогрессии Фибоначчи. Например, можно ли задать эти начальные значения равными 0 и -1? Необходимо использование определенного механизма управления и предотвращения возможности ввода неверных данных или возникновения ошибок, которые могут проявиться в ходе выполнения Java-программы. Данные темы будут рассмотрены далее.

Г* Программа тестирования классов прогрессии 7 class Tester {

public static void ,main(String[ ] args) { Progression prog; // проверяет ArithProgression

System.out.printlnf Arithmetic progression with default increment: prog = new ArithProgression(); prog.printProgression(10);

System, out. printlnf Arithmetic progression with increment 5:"); prog = new ArithProgression(5); prog.printProgression(10); // проверяет GeomProgression

System.out.printlnfGeometnc progression with default base:"); prog = new GeomProgression(); prog.printProgression(10);

System.out. printlnfGeometric progression with base 3:"); prog = new GeomProgression(3); prog.printProgression(10); // проверяет FibonacciProgression

System.out. printlnfFibonacci progression with default start values:"); prog = new FibonacciProgressionQ; prog.printProgression(IO);

System, out. printlnfFibonacci progression with start values 4 and 6:"); prog = new FibonacciProgression(4,6);

prog.printProgression(10); }

}

Фрагмент кода 2.5. Программа тестирования классов прогрессии

Arithmetic progression with default increment (Арифметическая прогрессия с заданным по умолчанию значением разности): 0123456789 Arithmetic progression with inctement 5 (Арифметическая прогрессия со значением разности 5): 0 5 10 15 20 25 30 35 40 45 Geometric progression with default base (Геометрическая профессия с заданным по умолчанию значением знаменателя): 2 4 8 16 32 64 128 256 512 1024 Geometric progression with base 3 (Геометрическая прогрессия со значением знаменателя 3): 3 9 27 81 243 729 2187 6561 19683 59049 Fibonacci progression with default start values (Прогрессия Фибоначчи с заданными по умолчанию первыми значениями): 0 1 1 2 3 5 8 13 21 34 Fibonacci progression with start values 4 and 6 (Прогрессия Фибоначчи с начальными значениями 4 и 6): 4 6 10 16 26 42 68 110 178 288

Фрагмент кода 2.6. Результат выполнения программы Tester, представленной фрагментом кода 2.5

Источник: Гудрич М.Т. Г93 Структуры данных и алгоритмы в Java / М.Т. Гудрич, Р. Тамассия; Пер. с англ. A.M. Чернухо. — Мн.: Новое знание, 2003. — 671 е.: ил.

По теме:

  • Комментарии