Порядок выполнения конструкторов

Порядок выполнения конструкторов

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

1. вызов конструктора базового класса;

2. присваивание исходных значений полям объекта посредством выполнения соответствующих выражений и блоков инициализации;

3. выполнение инструкций, предусмотренных в теле конструктора.

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

Перегружая(overloading) унаследованный метод базового класса, мы просто добавляем в объявление производного класса новый метод с тем же именем, но другой сигнатурой. Переопределяя(overriding) метод, мы изменяем его реализацию, так что при обращении к методу объекта производного класса будет вызвана именно новая версия метода, а не «старая», принадлежащая базовому классу.

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

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

При переопределении метода позволяется изменять и другие модификаторы. Признаками synchronized, native и strictfp разрешено манипулировать совершенно свободно, поскольку они относятся исключительно к особенностям внутренней реализации метода. Переопределённый метод может быть помечен как final, но тот, который подвергается переопределению – нет. Метод экзем-пляра производного класса не может обладать той же сигнатурой, что и статический унаследованный метод, и наоборот. Переопределённый метод в производном классе, однако, может быть снабжён модификатором abstract даже в том случае, если в базовом этого предусмотрено не было.

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

3. Сокрытие полей. Доступ к унаследованным членам. Возможность доступа и переопределение.

Поля класса не допускают переопределения – их можно только скрыть (hide). Когда в производном классе объявляется поле с тем же именем, что и в базовом, прежнее поле продолжает существовать, но перестаёт быть доступным, если обращаться к нему непосредственно по имени. Чтобы сослаться на одноимённое поле, принадлежащее базовому классу, следует воспользоваться служебным словом superлибо сослаться на текущий объект с приведением типа к базовому классу.

Доступ к унаследованным членам класса

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

Если же осуществляется обращение к полю, в рассмотрение принимается объявленный тип ссылки на объект, а не тип самого объекта.

Сокрытие статических членов

Статические члены класса не могут быть переопределены, поскольку всегда скрыты – как поля, так и методы. Обращение к каждому статическому полю или методу чаще всего осуществля-ется посредством задания имени класса, в котором они объявлены. Из факта сокрытия статического поля или метода в результате объявления одноимённого члена производного класса вытекает одно следствие – если для доступа к статическому члену используется ссылка, то, как и в ситуации с сокрытием полей, при выборе подхо-дящей альтернативы компилятор учитывает объявленный тип ссылки, а не тип объекта, на который она указывает.

Служебное слово super

Служебное слово superможет быть использовано в теле любого нестатического члена класса. При доступе к полю или вызове метода выражение superдействует как ссылка на текущий объект, представленный как экземпляр базового класса. Применение super– это единственный вариант, когда выбор метода обусловливается типом ссылки. Т.е. слово superпозволяет вызвать метод, который присутствует в непосредственном родительском классе. Оно не позволяет вызвать метод, если он отсутствует в родительском классе, и не позволяет вызвать более раннюю версию метода, чем описанная в непосредственном родительском классе.

Ключевое слово super может использоваться одним из трёх способов:

в первой строке конструкторов для вызова конкретного конструктора родительского класса (в форме super(аргументы));

в выражениях для обращения к полю из родительского класса (в форме super.имяПоля);

в выражениях для вызова метода из родительского класса (в форме super.имяМетода(аргументы)).

Слово superявляется единственным способом вызова переопределённого метода. «Извне» объекта (не в его методах) вызвать версию метода, который был переопределён, вообще нельзя.

5.Совместимость. Явное преобразование типов.

Совместимость

В любой ситуации, когда значение выражения присваивается некоторой переменной, тип выражения должен быть совместим (compatible) с типом переменной.

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

Те же правила действуют и в отношении выражений, которые возвращаются из тела метода инструкцией return: тип конкретного возвращаемого значения должен быть совместим с тем, который значится в объявлении метода.

Значение null– это специальный случай: nullпозволено присваивать переменным всех ссылочных типов, включая и массивы.

О типах, находящихся на более высоких ступеньках иерархии классов, говорят как о более широких, или менее конкретных, по сравнению с теми, которые расположены ниже. Соответственно, производные типы называют более узкими, или более конкретными, нежели их «прародители». Когда в выражении, предусматривающем использование объекта базового класса, применяется объект производного класса, имеет место преобразование с расширением типа. Подобная операция, допустимость которой проверяется на этапе компиляции, приводит к тому, что объект производного типа интерпретируется программой как объект базового класса.

Обратное действие, когда ссылка на объект базового класса преобразуется в ссылку на объект производного класса, называют преобразованием с сужением типа. В этом случае следует явно применить оператор преобразования (casting) типов.

Явное преобразование типов

Оператор преобразования типов позволяет сообщить компилятору, что конкретное выражение следует трактовать таким образом, будто оно относится к тому типу, который явно указан. Оператор преобразования типов представляет собой конструкцию, состоящую из наименования требуемого типа, заключённого в круглые скобки, которую размещают непосредственно перед выражением, подлежащим преобразованию.

Даже в том случае, если подлинный тип объекта, «скрывающегося» за ссылкой, нам известен, его всё ещё следует сообщить компилятору, обратившись к оператору преобразования типов.

Преобразование с расширением типа нередко обозначают терминами преобразование вверх(upcasting) или безопасное преобразование, поскольку тип, расположенный на низкой ступени иерархии, приводится к классу более высокого уровня, а подобная операция заведомо допустима. Преобразование с сужением типа соответственно называют преобразованием вниз(downcasting) – воздействию подвергается объект базового типа, который должен быть интерпретирован как объект производного типа. Преобразование вниз – это ещё и небезопасное преобразование, поскольку его результат в общем случае может быть неверен.

Когда компилятор, анализируя исходный текст программы, встречает выражение явного преобразования типов, он всегда проверяет корректность операции. Если выясняется, что операция некорректна ещё на этапе компиляции, выдаётся соответствующее сообщение об ошибке. Если же компилятор не в состоянии сразу подтвердить возможность преобразования или опровергнуть её, он добавляет в код дополнительные инструкции, призванные проверить код во время его выполнения. Когда подобный контроль даёт отрицательный результат, генерируется исключение типа ClassCastException.

6. Проверка типа.

Часто возникает задача определения принадлежности объекта тому или иному типу, и решить её позволяет оператор instanceof, возвращающий в результате вычисления значение true, если выражение левой части совместимо с типом, название которого указано в правой части, и false– в противном случае. Следует иметь в виду, что nullнельзя причислить к какому бы то ни было типу, и поэтому результат применения instanceofпо отношению к nullвсегда равен false.

Конструкция проверки типов с помощью оператора instanceofособенно полезна, когда методу, как правило, не требуется передавать аргумент более широкого типа, но если подобное всё-таки происходит, метод обязан отреагировать должным образом.

Например, некий метод сортировки sortкак предусмотрено в его объявлении, обычно работает с объектом класса List, но если в качестве аргумента передаётся ссылка на объект SortedList, который уже отсор-тирован, делать далее уже ничего не нужно.

Применениепроверкитипа

public static void sort(List list) {

if (list instanceofSortedList)

return; // уже всё готово

else

// сортировка списка

}

7. Методы и классы final.

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

Класс, помеченный как final, не поддаётся наследованию и все его методы косвенным образом приобретают свойство final.

Применение признака finalв объявлениях классов и методов способно повысить уровень безопасности кода.

Во многих случаях для достижения достаточного уровня безопасности кода вовсе нет необходимости обозначать весь класс как final– вполне возможно сохранить способность класса к расширению, пометив модификатором finalтолько его «критические» структурные элементы. Разумеется, поля, к которым обращается код методов final, должны быть в свою очередь помечены как finalили private, так как в противном случае любой производный класс получит возможность изменить их содержимое, воздействуя на поведение соответствующих методов.

Ещё один эффект применения модификатора finalсвязан с упрощением задачи оптимизации кода, решаемой компилятором.

Когда вызывается метод, не помеченный как final, исполняющая система определяет фактический класс объекта, связывает вызов с наиболее подходящим кодом из группы перегруженных методови передаёт управление этому коду. Но если метод, например, getName(), обозначен как final, операция обращения к нему упрощается. В самом простом случае, подобном тому, который касается getName(), компилятор может заменить вызов метода кодом его тела. Такой механизм носит название встраивания кода(inlining).

Использование модификатора finalв объявлениях классов способствует также повышению эффективности некоторых операций проверки типов.

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

8. Методы и классы abstract.

Абстрактные классы находят широкое применение в тех случаях, когда, например, некоторые признаки поведения класса приемлемы для всех его объектов, но при этом существуют и такие функциональные особенности, которые целесообразно реализовать только в определённых производных классах, а не в базовом классе непосредственно. Объявления подобных классов снабжают модификатором abstract, тем же признаком помечают и методы класса, в объявлении которого отсутствует блок тела.

Любой класс, содержащий методы с модификатором abstract, сам должен иметь модификатор abstract.

Абстрактный метод должен быть переопределён в любом производном классе, если только тот не помечен как abstract.

В любом производном классе позволено переопределять конкретные методы базового класса, помечая их признаком abstract.

Объекты абстрактного класса нельзя создавать, поскольку заведомо известно, что какие-то его методы, которые могут быть вызваны прикладной программой, не реализованы.

9. Класс Object. Методы класса Object. Клонирование объектов.

Класс Object

Класс Objectнаходится на вершине иерархии классов Java. Objectявно или косвенно наследуется всеми классами, поэтому переменная типа Objectспособна указывать на объект любого типа, будь то экземпляр какого-либо класса или массив. Правда, переменной типа Objectнельзя непосредственно присваивать значения простых типов (таких как int, booleanи т.п.), но эти ограничения легко обойти, «запаковав» значения в объекты соответствующих классов-оболочек (Integer, Booleanи др.).

Метод сравнения объектов

Метод сравнения объектов имеет следующее объявление:

publicboolean equals(Object obj)

Метод проверяет, равны ли текущий объект и объект, на который указывает ссылка obj, переданная в качестве параметра, и возвращает значение true, если факт равенства установлен, и false– в противном случае.

Если необходимо проверить, указывают ли две ссылки на один и тот же объект, следует применять операторы == или!=. Метод equals() сопоставляет содержимоеобъектов.исходной реализации метода equals(), предусмотренной в классе Object, предполагается, что объект равен только самому себе, т.е.удовлетворяет условию this == obj.

Метод вычисления хеш-кода

Метод вычисления хеш-кода имеет следующее объявление:

publicinthashCode()

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

Методы вычисления хеш-кода и сравнения объектов связаны: «равные» объекты должны иметь одинаковые значения хеш-кодов (обратное, вообще говоря, неверно). Поэтому при переопределении одного из этих методов следует переопределять и другой.

Метод клонирования объектов

Метод клонирования объектов имеет следующее объявление:

protected Object clone() throws CloneNotSupportedException

Методвозвращаетклонтекущегообъекта. В классе Objectметод clone() является защищенным. Чтобы объекты конкретного класса можно было клонировать, метод clone() реализуется в этом конкретном классе.

Существует ряд соглашений, регламентирующих реализацию метода clone():

- Класс должен реализовывать интерфейс-маркер (пустой интерфейс) Cloneable. Метод Object.clone() проверяет, реализован ли в классе, которому принадлежит текущий объект, интерфейcCloneable, и выбрасывает исключение типа CloneNotSupportedException, если ответ отрицательный.

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

- Результат клонирования должен быть получен вызовом super.clone().

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

Порядок выполнения конструкторов

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

1. вызов конструктора базового класса;

2. присваивание исходных значений полям объекта посредством выполнения соответствующих выражений и блоков инициализации;

3. выполнение инструкций, предусмотренных в теле конструктора.

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

Перегружая(overloading) унаследованный метод базового класса, мы просто добавляем в объявление производного класса новый метод с тем же именем, но другой сигнатурой. Переопределяя(overriding) метод, мы изменяем его реализацию, так что при обращении к методу объекта производного класса будет вызвана именно новая версия метода, а не «старая», принадлежащая базовому классу.

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

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

При переопределении метода позволяется изменять и другие модификаторы. Признаками synchronized, native и strictfp разрешено манипулировать совершенно свободно, поскольку они относятся исключительно к особенностям внутренней реализации метода. Переопределённый метод может быть помечен как final, но тот, который подвергается переопределению – нет. Метод экзем-пляра производного класса не может обладать той же сигнатурой, что и статический унаследованный метод, и наоборот. Переопределённый метод в производном классе, однако, может быть снабжён модификатором abstract даже в том случае, если в базовом этого предусмотрено не было.

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

3. Сокрытие полей. Доступ к унаследованным членам. Возможность доступа и переопределение.

Поля класса не допускают переопределения – их можно только скрыть (hide). Когда в производном классе объявляется поле с тем же именем, что и в базовом, прежнее поле продолжает существовать, но перестаёт быть доступным, если обращаться к нему непосредственно по имени. Чтобы сослаться на одноимённое поле, принадлежащее базовому классу, следует воспользоваться служебным словом superлибо сослаться на текущий объект с приведением типа к базовому классу.

Наши рекомендации