Помощничек
Главная | Обратная связь


Археология
Архитектура
Астрономия
Аудит
Биология
Ботаника
Бухгалтерский учёт
Войное дело
Генетика
География
Геология
Дизайн
Искусство
История
Кино
Кулинария
Культура
Литература
Математика
Медицина
Металлургия
Мифология
Музыка
Психология
Религия
Спорт
Строительство
Техника
Транспорт
Туризм
Усадьба
Физика
Фотография
Химия
Экология
Электричество
Электроника
Энергетика

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



Когда иерархия классов создана, в каком порядке вызываются конструкторы классов, образующих иерархию? Например, если имеется подкласс 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-конструктора.

 

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

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

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

В иерархии классов, если метод в подклассе имеет такое же имя и сигнатуру типов (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() суперкласса.

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

Спецификатор 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.

Класс Object

В Java определен один специальный класс – Object. Все другие классы являются его подклассами, Object – это суперкласс всех других классов. Это означает, что ссылочная переменная типа Object может обращаться к объекту любого другого класса. Кроме того, т.к. массивы реализуются как классы, переменная типа Object может также обращаться к любому массиву.

Object определяет методы (табл. 1), что означает, что они доступны в каждом объекте.

 

Таблица 1. Методы Object

Метод Цель
Object clone() Создает новый объект, который является таким же, как имитируемый объект
boolean equals (Object object) Определяет, является ли один объект равным другому
void finalize() Вызывается прежде, чем неиспользованный объект будет переработан (сборщиком мусора)
Claas getclass() Получает класс объекта во время выполнения
int hashCode() Возвращает хэш-код, связанный с вызовом объекта
void notify() Возобновляет выполнение потока, ожидающего на объекте вызова
void notifyAU() Возобновляет выполнение всех потоков, ожидающих на объекте вызова
String tostring() Возвращает строку, которая описывает объект
void wait () void wait (long millisrconds) void wait (long millisrconds, int nanoseconds) Ждет выполнения на другом потоке

 

Методы getclass(), notify(), notifyAll() и wait() объявлены как final.

Другие можно переопределять. Здесь отметим два метода: equals() и toString(). Метод equals() сравнивает содержимое двух объектов. Он возвращает true, если объекты эквивалентны, и false – в противном случае. Метод ToString() возвращает строку, содержащую описание объекта, на котором он вызывается. Кроме того, этот метод вызывается автоматически, когда объект выводится методом printin(). Много классов переопределяют данный метод, что позволяет им приспосабливать описание специально для типов объектов, которые они создают.

 

Контрольные вопросы

1. Понятие наследования. Графический пример.

2. Перегрузка методов. Пример.

3. Определение перегруженного метода.

4. Вызов перегруженного конструктора.

5. Способы передачи аргументов.

6. Особенности статических и не статических вложенных классов.

7. Наследование классов. Пример.

8. Модификаторы доступа и их назначение.

9. Формы использования ключевого слова super.

10. Понятие многоуровневой иерархии.

11. Работа super конструктора в многоуровневой иерархии.

12. Порядок вызова конструкторов при многоуровневой иерархии.

13. Видимость методов при наследовании.

14. Формы использования ключевого слова final.

15. Методы высшего суперкласса.

16. Множественное наследование.

 

 




Поиск по сайту:

©2015-2020 studopedya.ru Все права принадлежат авторам размещенных материалов.