Главная страница

Лабораторная работа 1 2 лабораторная работа 2 31 лабораторная работа 3 44 лабораторная работа 4 74


Скачать 1.76 Mb.
НазваниеЛабораторная работа 1 2 лабораторная работа 2 31 лабораторная работа 3 44 лабораторная работа 4 74
Дата12.03.2021
Размер1.76 Mb.
Формат файлаdoc
Имя файлаOOP_Lab_Rus.doc
ТипЛабораторная работа
#184105
страница22 из 31
1   ...   18   19   20   21   22   23   24   25   ...   31

5.13 Когда вызываются конструкторы


Когда иерархия классов создана, в каком порядке вызываются конструкторы классов, образующих иерархию? Например, если имеется подкласс B и суперкласс A, A-конструктор вызывается перед B-конструкторами, или наоборот? Ответ заключается в том, что в иерархии классов конструкторы вызываются в порядке подчиненности классов – от суперкласса к подклассу. Далее, так как super() должен быть первым оператором, выполняемым в конструкторе подкласса, этот порядок всегда одинаков и не зависит от того, используется ли super(). Если super() не используется, то сначала будет, выполнен конструктор по умолчанию (без параметров) каждого суперкласса. Следующая программа иллюстрирует, когда выполняются конструкторы:
// Демонстрирует порядок вызова конструкторов.

// Создать суперкласс A.

class A {

A() {

System.out.println("Внутри A-конструктора.");

}

}

// Создать подкласс B расширением A.

class B extends A {

B() {

System.out.println("Внутри B-конструктора.");

}

}

// Создать другой класс (C), расширяющий B.

class C extends B {

C() {

System.out.println("Внутри C-конструктора.");

}

}

class CallingCons {

public static void main(String args[]) {

C c = new C();

}

}
Вывод этой программы:
Внутри A-конструктора.

Внутри B-конструктора.

Внутри C-конструктора.
Конструкторы вызываются в порядке подчиненности классов.

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

5.14 Переопределение методов


В иерархии классов, если метод в подклассе имеет такое же имя и сигнатуру типов (type signature), как метод в его суперклассе, то говорят, что метод в поклассе переопределяет (override) метод в суперклассе. Когда переопределенный метод вызывается в подклассе, он будет всегда обращаться к версии этого метода, определенной подклассом. Версия метода, определенная суперклассом, будет скрыта. Рассмотрим следующий фрагмент:
// Переопределение методов.

class A {

int i, j;

A(int a, int b) {

i = a;

j = b;

}

// показать i и j на экране

void show() {

System.out.println("i и j: " + i + " " + j);

}

}

class B extends A {

int k;

B(int a, int b, int c) {

super(a, b);

k = c;

}

// Показать на экране k (этот show() переопределяет show() из A)

void show() {

System.out.println("k: " + k);

}

}
class Override {

public static void main(String args[]) {

B subOb = new B(1, 2, 3);
subOb.show(); // здесь вызывается show() из B

}

}
Вывод этой программы:
k: 3
Когда show() вызвается для объекта типа B, используется версия show(), определенная в классе B. То есть версия show() внутри B переопределяет (отменяет) версию, объявленную в A.

Если нужно обратиться к версии суперкласса переопределенной функции, то можно сделать это, используя super. Например, в следующей версии подкласс B вызывается show() версия суперкласса A. Это позволяет отобразить все экземплярные переменные.
class B extends A {

int к;

В (int а, int b, int с) {

super(a, b);

k = с;

}
void show () {

super.show(); // вызов show() суперкласса A

System.out.println("k: " + k);

}

}
Если вы замените этой версией класс B предыдущей программы, то получите следующий вывод:
i и j: 1 2

k: 3
Здесь super, show() вызывает версию show() суперкласса.

Переопределение метода происходит только тогда, когда имена и сигнатуры типов этих двух методов идентичны. Если это не так, то оба метода просто перегружены.

5.15 Спецификатор final


Ключевое слово final имеет три применения. Первое – его можно использовать для создания эквивалента именованной константы. Два других применения final связаны с наследованием.

1. Переменную можно объявить как final, предохраняя ее содержимое от изменения. Это означает, что нужно инициализировать final-переменную, когда она объявляется (в таком применении final подобен const в C/C++). Например:
final int FILE_NEW = 1;

final int FILE_OPEN = 2;

final int FILE_SAVE = 3;

final int FILE_SAVEAS = 4;

final int FILE_QUIT = 5;
После таких объявлений последующие части программы могут использовать FILE_OPEN и т.д., как константы, без опасения, что их значения были изменены.

Общее соглашение кодирования для final-переменных – выбирать все идентификаторы в верхнем регистре. Переменные, объявленные как final, не занимают память на «поэкземплярной» основе. Таким образом, final-переменная — по существу константа.

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

2. Использование final для отказа от переопределения.

Хотя переопределение методов – одно из наиболее мощных свойств Java, может появиться потребность отказаться от него. Чтобы отменить переопределение метода, укажите модификатор final в начале его объявления. Методы, объявленные как final, не могут переопределяться. Следующий фрагмент иллюстрирует final в таком применении:
class A {

final void meth() {

System.out.println("Это метод final.");

}

}

class В extends A {

void meth() { // СШИБКА! Нельзя переопределять.

System.out.println("Ошибка!");

}

}
Поскольку meth() объявлен как final, он не может быть переопределен в классе B. Если вы попытаетесь сделать это, то получите ошибку во время компиляции.

Методы, объявленные как final, могут иногда улучшать эффективность. Компилятору предоставлено право выполнять встроенные (inline) вызовы таких методов, потому что он «знает», что те не будут переопределяться подклассом. Когда речь идет о небольшой, например, final-функции, то компилятор Java часто может копировать (встраивать) байт-код подпрограммы прямо в точку вызова откомпилированного кода вызывающего метода, устраняя таким образом дополнительные затраты времени, связанные с обычным вызовом (невстроенного метода). Встраивание допустимо только для final-методов. Обычно Java организует вызовы методов динамически, во время выполнения. Это называется поздним связыванием (вызова с вызываемой функцией). Однако, т.к. final-методы не являются переопределяемыми, их вызов может быть организован во время компиляции. Это называется ранним связыванием.

3. Использование final для отмены наследования.

Иногда нужно разорвать наследственную связь классов (отменить наследование одного класса другим). Чтобы сделать это, предварите объявление класса ключевым словом final, что позволит неявно объявить и все его методы. Заметим, что недопустимо объявлять класс одновременно как abstract и final, т.к. абстрактный класс неполон сам по себе и полагается на свои подклассы, чтобы обеспечить полную реализацию. Пример final-класса:
final class A {

// ... .

}

// Следующий класс незаконный.

class B extends A { //ошибка! B не может быть подклассом A

// ...

}
Комментарий здесь означает, что B не может наследовать A, т.к. A объявлен как final.
1   ...   18   19   20   21   22   23   24   25   ...   31


написать администратору сайта