Смекни!
smekni.com

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

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

2.2. Работа со ссылочными переменными. Сборка мусора

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

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

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

Следует отметить, что сами объекты безымянны, и доступ к ним осуществляется только через ссылочные переменные. Часто говорят про ссылочную переменную как про сам объект, поскольку долго и неудобно произносить “объект, на который ссылается данная переменная”. Но этого по мере возможности следует избегать.

Ссылочной переменной любого типа может быть присвоено значение null, означающее, что она никуда не ссылается. Попытка доступа к объекту через такую переменную вызовет ошибку. В Java все ссылочные переменные первоначально инициируются значением null, если им не назначена ссылка на объект прямо в месте объявления. Очень часто встречающаяся ошибка – попытка доступа к полю или методу с помощью ссылочной переменной, которой не сопоставлен объект. Такая ошибка не может быть обнаружена на этапе компиляции и является ошибкой времени выполнения (Run-time error). При этом приложение Java генерирует исключительную ситуацию (ошибку) попытки доступа к объекту через ссылку null с сообщением следующего вида:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException.

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

Объект


Ссылочная переменная

Если переменная является ссылочной, из неё выходит стрелочка-ссылка. Она кончается на том объекте, на который указывает ссылка. Если в ссылочной переменной содержится значение null (ссылка “в никуда”, адрес==0), рисуется “висящая” короткая стрелка, у которой находится надпись “null”. Отметим, что в Java символом равенства является “==”, а не символ “=”, который используется для оператора присваивания.

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

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

Рассмотрим этот процесс подробнее. Будем считать, что сначала в ячейке circle1 типа Circle хранится нулевой адрес (значение ссылки равно null). Будем изображать это как стрелку в никуда с надписью null.

Переменная

circle1 типа Circle


null

После оператора

circle1=new Circle(x1,y1,r1) ;

в динамически выделенной безымянной ячейке памяти будет создан объект-окружность с координатами центра x1, y1 и радиусом r1 (это какие-то значения, конкретная величина которых в данном случае не имеет значения):

Объект1 типа Circle


circle1


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

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

circle2= circle1;

Такие присваивания изменяют адреса в ячейках ссылочных переменных, но не меняют содержания объектов.

Рассмотрим следующий участок кода:

Circle circle1=new Circle(x1,y1,r1) ;

Circle circle2=new Circle(x2,y2,r2) ;

Circle circle3;

Ему соответствует следующий рисунок:

Объект1 типа Circle


circle1

Объект2 типа Circle


circle2


circle3


null

Проведём присваивание

circle3=circle2;

В результате получится такая картинка:

Объект1 типа Circle


circle1

Объект2 типа Circle


circle2

circle3


Обе переменные, как circle2, так и circle3, теперь ссылаются на один и тот же объект – в них находится один и тот же Адрес2. То есть оба имени – синоним имени Объекта2. Напомним, что сам объект, как все динамически создаваемые величины, безымянный. Таким образом, circle2.x даст значение x2, точно так же, как и circle3.x. Более того, если мы изменим значение circle2.x, это приведёт к изменению circle3.x – ведь это одно и то же поле x нашего Объекта2.

Рассмотрим теперь, что произойдёт при присваивании