Смекни!
smekni.com

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

double mult1(double x, final double y){

x++;

return x*y;

}

А вот при компиляции такого кода будет выдано сообщение об ошибке:

double mult1(double x, final double y){

x++;

y++;

return x*y;

}

Локальные и глобальные переменные. Модификаторы доступа и правила видимости. Ссылка this

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

- Во-первых, при вызове в списке параметров не видно, что идёт обращение к соответствующим переменным, и программа становится “непрозрачной” для программиста. Что делает её неструктурной.

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

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

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

private - элемент (поле данных или метод) доступен только в методах данного класса. Доступа из объектов нет! То есть если мы создали объект, у которого имеется поле или метод private, то получить доступ к этому полю или методу из объекта нельзя.

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

Иногда, по аналогии с C++, этот тип доступа называют “дружественным”.

protected - элемент доступен только в методах данного класса, данного пакета, а также классах-наследниках (они могут располагаться в других пакетах).

public - элемент доступен из любых классов и объектов (с квалификацией именем пакета, если соответствующий класс не импортирован).

Например, в классе

class Vis1 {

private int x=10,y=10;

int p1=1;

protected int p2=1;

public int p3=1;

}

заданы переменные x,y,p1,p2,p3. Причём x и y обладают уровнем доступа private, p1 – пакетным, p2 – protected, p3 – public. Перечисление однотипных переменных через запятую позволяет использовать для нескольких переменных однократное задание имени типа и модификаторов, без повторений.

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

А вот переменные, заданные на уровне класса (глобальные переменные), создаются при создании объекта для методов объекта, и при первом вызове класса для переменных класса. И их можно использовать в методах данного класса как глобальные независимо от того, заданы переменные до метода или после.

Ещё одной важной особенностью локальных переменных является время их существования: под них выделяется память в момент вызова, а высвобождается сразу после окончания вызова. Рассмотрим функцию, вычисляющую сумму чисел от 1 до n:

double sum1(int n){

int i;

double r=0;

for(i=1;i<=n;i++){

r+=i;

};

return r;

}

Вызов данного метода может выглядеть так:

c=obj1.sum1(1000);

При этом переменные i и r существуют только во время вызова obj1.sum1(1000). При следующем аналогичном вызове будет создан, а затем высвобожден из памяти следующий комплект i и r.

Всё сказанное про локальные переменные также относится и к объектным переменным. Но не следует путать переменные и объекты: время жизни объектов гораздо больше. Даже если объект создаётся во время вызова подпрограммы, а после окончания этого вызова связь с ним кончается. Уничтожением неиспользуемых объектов занимается сборщик мусора (garbage collector). Если же объект создан в подпрограмме, и ссылка на него передана какой-либо глобальной переменной, он будет существовать после выхода из подпрограммы столько времени, сколько необходимо для работы с ним.

Остановимся на области видимости локальной переменной. Имеются следующие уровни видимости:

- На уровне метода. Переменная видна от места декларации до конца метода.

- На уровне блока. Если переменная задана внутри блока {…}, она видна от места декларации до конца блока. Блоки могут быть вложены один в другой с произвольным уровнем вложенности.

- На уровне цикла for. Переменная видна от места декларации в секции инициализации до конца тела цикла.

Глобальные переменные видны во всей подпрограмме.

Каждый объект имеет поле данных с именем this (“этот” – данное не слишком удачное обозначение унаследовано из C++), в котором хранится ссылка на сам этот объект. Поэтому доступ в методе объекта к полям и методам этого объекта может осуществляться либо напрямую, либо через ссылку this на этот объект. Например, если у объекта имеется поле x и метод show(), то this.x означает то же, что x, а this.show() – то же, show(). Но в случае перекрытия области видимости, о чём речь пойдёт чуть ниже, доступ по короткому имени оказывается невозможен, и приходится использовать доступ по ссылке this. Отметим, что ссылка this позволяет обойтись без использования имени объектной переменной, что делает код с её использованием более универсальным. Например, использовать в методах того класса, экземпляром которого является объект.

Ссылка this не может быть использована в методах класса (то есть заданных с модификатором static), поскольку они могут вызываться без существующего объекта.

Встаёт вопрос о том, что произойдёт, если на разных уровнях будет задано две переменных с одним именем. Имеется следующие варианты ситуаций:

- В классе имеется поле с некоторым именем (глобальная переменная), и в списке параметров задаётся локальная переменная с тем же именем. Такая проблема часто возникает в конструкторах при инициализации полей данных и в методах установки значений полей данных setИмяПоля. Это разрешено, и доступ к параметру идёт по имени, как обычно. Но при этом видимость поля данных (доступ к полю данных по его имени) перекрывается, и приходится использовать ссылку на объект this. Например, если имя поля данных x, и имя параметра в методе тоже x, установка значения поля выглядит так:

void setX(double x){

this.x=x

}

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

- В классе имеется поле с некоторым именем (глобальная переменная), и в секции инициализации цикла for или внутри какого-нибудь блока, ограниченного фигурными скобками {…}, задаётся локальная переменная с тем же именем. В Java такая ситуация разрешена. При этом внутри цикла или блока доступна заданная в нём локальная переменная, а глобальная переменная видна через ссылку this.

- Имеется локальная переменная (возможно, заданная как элемент списка параметров), и в секции инициализации цикла for или внутри какого-нибудь блока, ограниченного фигурными скобками {…}, задаётся локальная переменная с тем же именем. В Java такая ситуация запрещена. При этом выдаётся ошибка компиляции с информацией, что переменная с таким именем уже задана (“is already defined”).

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

Передача ссылочных типов в функции. Проблема изменения ссылки внутри подпрограммы

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

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

public class Location {

public int x=0,y=0;

public Location (int x, int y) {

this.x=x;

this.y=y;

}

}

А в классе приложения напишем следующий код:

Location locat1=new Location(10,20);

public static void m1(Location obj){

obj.x++;

obj.y++;

}

Мы задали переменную locat1 типа Location, инициализировав её поля x и y значениями 10 и 20. А в методе m1 происходит увеличение на 1 значения полей x и y объекта, связанного с формальным параметром obj.

Создадим две кнопки с обработчиками событий. Нажатие на первую кнопку будет приводить к выводу информации о значениях полей x и y объекта, связанного с переменной locat1. А нажатие на вторую – к вызову метода m1.