Смекни!
smekni.com

Общие представления о языке Java 5 (стр. 41 из 68)

Также имеются методы notify(), notifyAll(), и несколько перегруженных вариантов метода wait, предназначенные для работы с потоками (threads). О них говорится в разделе, посвящённом потокам.

Конструкторы. Зарезервированные слова super и this. Блоки инициализации

Как уже говорилось, объекты в Java создаются с помощью зарезервированного слова new, после которого идёт конструктор – специальная подпрограмма, занимающаяся созданием объекта и инициализацией полей создаваемого объекта. Для него не указывается тип возвращаемого значения, и он не является ни методом объекта (вызывается через имя класса когда объекта ещё нет), ни методом класса (в конструкторе доступен объект и его поля через ссылку this). На самом деле конструктор в сочетании с оператором new возвращает ссылку на создаваемый объект и может считаться особым видом методов, соединяющим в себе черты методов класса и методов объекта.

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

Если требуется инициализация, обычно применяют конструкторы со списком параметров. Примеры таких конструкторов рассматривались нами для классов Dot и Circle. Классы Dot и Circle были унаследованы от абстрактных классов, в которых не было конструкторов. Если же идёт наследование от неабстрактного класса, то есть такого, в котором уже имеется конструктор (пусть даже и конструктор по умолчанию), возникает некоторая специфика. Первым оператором в конструкторе должен быть вызов конструктора из суперкласса. Но его делают не через имя этого класса, а с помощью зарезервированного слова super (от “superclass”), после которого идёт необходимый для прародительского конструктора список параметров. Этот конструктор инициализирует поля данных, которые наследуются от суперкласса (в том числе и от всех более ранних прародителей). Например, напишем класс FilledCircle -наследник от Circle, экземпляр которого будет отрисовываться как цветной круг.

package java_gui_example;

import java.awt.*;

public class FilledCircle extends Circle{

/** Creates a new instance of FilledCircle */

public FilledCircle(Graphics g,Color bgColor, int r,Color color) {

super(g,bgColor,r);

this.color=color;

}

public void show(){

Color oldC=graphics.getColor();

graphics.setColor(color);

graphics.setXORMode(bgColor);

graphics.fillOval(x,y,size,size);

graphics.setColor(oldC);

graphics.setPaintMode();

}

public void hide(){

Color oldC=graphics.getColor();

graphics.setColor(color);

graphics.setXORMode(bgColor);

graphics.fillOval(x,y,size,size);

graphics.setColor(oldC);

graphics.setPaintMode();

}}

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

В данном классе мы применяем более совершенный способ отрисовки и “скрывания” фигур по сравнению с предыдущими классами. Он основан на использовании режима рисования XOR (“исключающее или”). Установка этого режима производится методом setXORMode. При этом повторный вывод фигуры на то же место приводит к восстановлению первоначального изображения в области вывода. Переход в обычный режим рисования осуществляется методом setPaintMode.

В конструкторах очень часто используют зарезервированное слово this для доступа к полям объекта, видимость имён которых перекрыта переменными из списка параметров конструктора. Но в конструкторах оно имеет ещё одно применение - для обращения из одного варианта конструктора к другому, имеющему другой список параметров. Напомним, что наличие таких вариантов называется перегрузкой конструкторов. Например, пусть мы первоначально задали в классе Circle конструктор, в котором значение полей x, y и r задаётся случайным образом:

Circle(Graphics g, Color bgColor){

graphics=g;

this.bgColor=bgColor;

size=(int)Math.round(Math.random()*40);

}

Тогда конструктор, в котором случайным образом задаются значения полей x и y, а значение size задаётся через список параметров конструктора, можно написать так:

Circle(Graphics g, Color bgColor, int r){

this(g, bgColor);

size=r;

}

При вызове конструктора с помощью слова this требуется, чтобы вызов this был первым оператором в реализации вызывающего конструктора.

В отличие от языка C++ в Java не разрешается использование имени конструктора, отличающегося от имени класса.

Порядок вызовов при создании объекта некого класса (будем называть его дочерним классом):

- Создаётся объект, в котором все поля данных имеют значения по умолчанию (нули на двоичном уровне представления).

- Вызывается конструктор дочернего класса.

- Конструктор дочернего класса вызывает конструктор родителя (непосредственного прародителя), а также по цепочке все прародительские конструкторы и инициализации полей, заданных в этих классах, вплоть до класса Object.

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

- Выполняется тело конструктора родительского класса.

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

- Выполняется тело конструктора дочернего класса.

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

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

Модификаторы class ИмяКласса extends ИмяРодителя {

Задание полей;

static {

тело блока инициализации класса

}

{

тело блока инициализации объекта

}

Задание подпрограмм - методов класса, методов объекта, конструкторов

}

Блоков инициализации класса и блоков инициализации объекта может быть несколько.

Порядок выполнения операторов при наличии блоков инициализации главного класса приложения (содержащего метод main):

- инициализация полей данных и выполнение блоков инициализации класса (в порядке записи в декларации класса);

- метод main;

- выполнение блоков инициализации объекта;

- выполнение тела конструктора класса.

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

- инициализация полей данных и выполнение блоков инициализации класса (в порядке записи в декларации класса);

- метод main;

- выполнение блоков инициализации объекта;

- выполнение тела конструктора класса.

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

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

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

Например, у нас имеется список фигур, отрисовываемых на экране, и мы хотим удалить из этого списка какую-нибудь фигуру. Перед уничтожением фигура должна исключить себя из списка, затем дать команду списку заново отрисовать содержащиеся в нём фигуры, и только после этого “умереть”. Именно такого рода действия характерны для деструкторов. Заметим, что возможна другая логика работы: дать списку команду исключить из него фигуру, после чего перерисовать фигуры, содержащиеся в списке. Но желательно, чтобы язык программирования поддерживал возможность реализации обоих подходов.

В Java имеется метод finalize(). Если в классе , который производит завершающие действия перед уничтожением объекта сборщиком мусора, переопределить этот метод, он, как может показаться, может служить некой заменой деструктора. Но так как момент уничтожения объекта неопределёнен и может быть отнесён по времени очень далеко от момента потери ссылки на объект, метод finalize не может служить реальной заменой деструктору. Даже явный вызов сборщика мусора System.gk() сразу после вызова метода finalize() не слишком удачное решение, так как и в этом случае нет гарантии правильности порядка высвобождения ресурсов. Кроме того, сборщик мусора потребляет много ресурсов и в ряде случаев может приостановить работу программы на заметное время.

Гораздо более простым и правильным решением будет написать в базовом классе разрабатываемой вами иерархии метод destroy() - “уничтожить, разрушить”, который будет заниматься выполнением всех необходимых вспомогательных действий (можно назвать метод dispose() – “избавиться, отделаться”, можно free() – “освободить”). Причём при необходимости надо будет переопределять этот метод в классах-наследниках. В случае, когда надо вызывать прародительский деструктор, следует делать вызов super.destroy(). При этом желательно, чтобы он был последним оператором в деструкторе класса – в противном случае может оказаться неправильной логика работы деструктора. Например, произойдёт попытка обращения к объекту, исключённому из списка, или попытка записи в уже закрытый файл.