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


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

Скрытие переменной экземпляра



Как известно, в Java недопустимо объявление двух локальных переменных с одним и тем же именем внутри той же самой или включающей области действия идентификаторов. Заметим, что вы можете иметь локальные переменные, включая формальные параметры для методов, которые перекрываются с именами экземплярных переменных класса. Однако, когда локальная переменная имеет такое же имя, как переменная экземпляра, локальная переменная скрывает переменную экземпляра. Вот почему width, height и depth не использовались как имена параметров конструктора Box() внутри класса Box. Если бы они были использованы для именования этих параметров, то, скажем width, как формальный параметр, скрыл бы переменную экземпляра width. Хотя обычно проще указывать различные имена, существует другой способ обойти эту ситуацию. Поскольку this позволяет обращаться прямо к объекту, это можно применять для разрешения любых конфликтов пространства имен, которые могли бы происходить между экземплярными и локальными переменными. Ниже представлена другая версия Box(), которая использует width, height и depth для имен параметров и затем применяет this, чтобы получить доступ к переменным экземпляра с теми же самыми именами:

 

// Используйте этот вариант конструктора

// для разрешения конфликтов пространства имен.

Box(double width, double height, double depth) {

this.width = width;

this.height = height;

this.depth = depth;

 

Предупреждение.

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

 

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

Сборка «мусора»

Так как объекты распределяются динамически с помощью операции new, можно задать вопрос, как такие объекты ликвидируются и их память освобождается для более позднего перераспределения. В некоторых языках, таких как С++, от динамически распределенных объектов нужно освобождаться вручную – при помощи оператора delete. Java использует другой подход: он выполняет освобождение памяти от объекта автоматически. Методика, которая реализует эту процедуру, называется сборкой «мусора». Она работает примерно так: когда никаких ссылок на объект не существует, предполагается, что этот объект больше не нужен, и память, занятая объектом, может быть освобождена. Нет никакой явной потребности уничтожать объекты как в С++. Сборка «мусора» происходит не регулярно (если вообще происходит) во время выполнения программы. Она не будет происходить просто потому, что существует один или более объектов, которые уже не используются. Кроме того, различные реализации исполняющей системы Java имеют разные подходы к сборке «мусора».

Метод finalized

Иногда объекту нужно выполнять некоторые действия, когда он разрушается. Например, если объект содержит некоторый не-Java ресурс, такой как дескриптор файла или оконный шрифт, то нужно удостовериться, что до разрушения объекта эти ресурсы освобождаются. Для обработки таких ситуаций Java использует механизм, называемый завершением (finalization). Применяя завершение, можно определять специальные действия, которые будут выполняться примерно тогда, когда объект будет использоваться сборщиком мусора.

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

Метод finalize о имеет следующую общую форму:

 

protected void finalize() {

// код завершения

}

 

Здесь ключевое слово protected – спецификатор, который запрещает доступ к finalize() кодам, определенным вне этого класса.

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

 

Замечание. Если вы знакомы с С++, то знаете, что С++ позволяет определять деструктор для класса, который вызывается, когда объект выходит из области действия идентификаторов. Java не поддерживает этой идеи и не использует деструкторов. Метод finalize() только аппроксимирует функцию деструктора. Когда вы наберетесь больше опыта в Java, то увидите, что потребность в функциях деструктора минимальна из-за наличия в Java подсистемы сборки «мусора».

Класс Stack

Хотя класс Box полезен для иллюстрации существенных элементов класса, он имеет небольшое практическое значение. Чтобы показать действительную мощь классов, приведем более сложный пример. Как вы помните из объектно-ориентированного программирования (OOП), одним из наиболее важных его преимуществ является инкапсуляция данных и кода, который манипулирует этими данными. Механизмом, с помощью которого достигается инкапсуляция, является класс. Создавая класс, вы организуете новый тип данных, который определяет как характер данных, так и подпрограммы, используемые для манипулирования этими данными. Непротиворечивый и управляемый интерфейс с данными класса определяют методы. Таким образом, вы можете использовать класс через его методы, не беспокоясь о деталях его реализации или о том, как данные фактически управляются внутри класса. В некотором смысле, класс подобен «машине данных». Чтобы использовать машину через ее органы управления, никаких знаний о том, что происходит внутри машины, не требуется. Фактически, поскольку подробности скрыты, ее внутренняя работа может быть изменена так, как это необходимо. Пока ваш код использует класс через его методы, внутренние подробности могут изменяться, не вызывая побочных эффектов вне класса.

Чтобы получить практическое приложение предшествующего обсуждения, давайте разработаем один из типичных примеров инкапсуляции – стек. Стек хранит данные, используя очередь типа LIFO («Last-In, First-Out») – последним вошел, первым вышел. То есть стек подобен стопке тарелок на столе – последняя тарелка, поставленная на стопку, снимается со стопки первой. Стеки управляются через две операции, традиционно называемые push (поместить) и pop (извлечь, вытолкнуть). Чтобы поместить элемент в вершину стека, нужно использовать операцию push. Чтобы извлечь элемент из стека, нужно использовать операцию pop. Заметим, что инкапсуляция полного механизма стека – довольно простая задача.

В следующей программе класс с именем stack реализует стек целых чисел:

 

// Этот класс определяет целый стек для хранения 10 значений

class Stack {

int stck[] = new int[10];

int tos;

 

// инициализировать вершину стека

Stack() {

tos = -1;

}

 

// поместить элемент в стек

void push(int item) {

if(tos==9)

System.out.println("Стек заполнен.");

else

stck[++tos] = item;

}

 

// Извлечь элемент из стека

int pop() {

if(tos < 0) {

System.out.println("Стек пуст.");

return 0;

}

else

return stck[tos--];

}

}

 

Нетрудно видеть, что класс stack определяет два элемента данных и три метода. Стек целых чисел содержится в массиве stck. Этот массив индексирован переменной tos, которая всегда содержит индекс вершины стека. Конструктор stack() инициализирует tos значением -1, которое указывает, что стек пуст. Метод push() помещает элемент в стек. Чтобы извлечь элемент, вызовите метод рор(). Так как доступ к стеку выполняется через push() и рор(), тот факт, что стек содержится в массиве, не мешает использованию стека. Например, стек мог бы храниться в более сложной структуре данных, скажем, типа связного списка, а интерфейс, определенный методами push() и pop(), остался бы тем же самым.

Показанный ниже класс Teststack, демонстрирует работу с классом stack. Он создает два целочисленных стека, помещает некоторые значения в каждый и затем выталкивает их.

 

class TestStack {

public static void main(String args []) {

Stack mystack1 = new Stack();

Stack mystack2 = new Stack();

// поместить несколько чисел в стек

for(int i=0; i<10; i++) mystack1.push(i);

for(int i=10; i<20; i++) mystack2.push(i);

// вытолкнуть эти числа из стека

System.out.println("Стек в mystack1:");

for(int i=0; i<10; i++)

System.out.println(mystack1.pop()) ;

System.out.println("Стек в mystack2:");

for(int i=0; i<10; i++)

System.out.println(mystack2.pop());

}

}

 

Эта программа генерирует следующий вывод:

 

Стек в mystack1:

Стек в mystack2:

 

Нетрудно заметить, что содержимое каждого стека различно.

Наконец, последнее замечание относительно класса stack в данной реализации возможно изменение массива stack, который содержит стек, кодом, находящимся вне класса stack, оставляя его открытым для неправильного использования или повреждений.

Управление доступом

Напомним, что инкапсуляция связывает данные с кодом, который манипулирует ими. Однако инкапсуляция обеспечивает другой важный атрибут: управление доступом. Через инкапсуляцию можно управлять доступом различных частей программы к членам класса и предотвращать неправильное использование таких членов. Например, разрешая доступ к данным только через хорошо определенный набор методов, есть возможность предотвращения неверного использования этих данных. Таким образом, при правильной реализации класс создает для использования «черный ящик», но внутренняя работа, которого не доступна для вмешательства. Однако представленные ранее классы не полностью удовлетворяют этой цели. Например, рассмотрим класс stack. Методы push() и pop() обеспечивают управляемый интерфейс к стеку, интерфейс этот не предписан. То есть, другая часть программы может обойти указанные методы и получить прямой доступ к стеку. Конечно, в неумелых руках это может привести к неприятностям. Далее будет представлен механизм, с помощью которого можно точно управлять доступом к различным членам класса.

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

Спецификаторы доступа Java: public (общий), private (частный) и protected (защищенный). Java также определяет уровень доступа, заданный по умолчанию (default access level). Спецификатор protected применяется только при использовании наследования.

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

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

Спецификатор доступа предшествует остальной части описания типа элемента (члена) класса. То есть он должен начинать предложение объявления такого элемента. Например:

 

public int i;

private double j;

 

private int myMethod(int a, char b) { //

 

Чтобы понимать специфику общего и частного доступа, рассмотрим следующую программу:

 

/* Эта программа демонстрирует различие между

методами доступа public и private.

*/

class Test {

int a; // доступ по умолчанию (public)

public int b; // общий (public) доступ

private int c; // частный (private) доступ

// метода для доступа к переменной c

void setc(int i) { // установить значение c

c = i;

}

int getc() { // получить значение c

return c;

}

}

class AccessTest {

public static void main(String args[]) {

Test ob = new Test();

// OK, к переменным а и b возможен прямой доступ.

ob.a = 10; ob.b = 20;

// Не OK и вызовет ошибку.

// ob.c = 100; //Ошибка!

// Нужен доступ к c через ее методы.

ob.setc(100); //OK

System.out.println("a, b, и c: " + ob.a + " " + ob.b + " " + ob.getc());

}

}

 

Внутри класса Test используется доступ по умолчанию, который для этого примера эквивалентен указанию public. Член b явно определен как public.

Для члена с задан доступ private. Это означает, что к нему нельзя обращаться из кода, находящегося вне его класса. Так внутри класса AccessTest переменная c не может использоваться прямо. К ней нужно обращаться через ее public-методы setc() и getc(). Если бы вы удалили символ комментария в начале следующей строки, то не смогли бы откомпилировать эту программу из-за нарушения правил доступа:

 

// ob.с = 100; // Ошибка!

 

Чтобы увидеть, как управление доступом может применяться в более практическом примере, рассмотрим следующую улучшенную версию класса stack

 

// Этот класс определяет целый стек, который может содержать 10 значений.

class Stack {

/* Теперь как stck, так и tos есть private. Это значит,

что они не могут быть случайно или намеренно

изменены опасным для стека способом.

*/

private int stck[] = new int[10];

private int tos;

// инициализировать вершину стека

Stack () {

tos = -1;

}

// поместить элемент в стек

void push(int item) {

if (tos==9)

System.out.println("Стек заполнен.");

else

stck[++tos] = item;

}

// вытолкнуть элемент из стека

int pop() {

if (tos < 0) {

System.out.println("Стек пуст.") ;

return 0;

}

else

return stck[tos--];

}

}

 

Теперь переменная stck, которая содержит стек, и tos, которая является индексом вершины стека, определены как private. Это означает, что к ним нельзя обращаться или изменять их, кроме как через методы push() и pop(). Делая tos частной, мы, например, предохраняем другие части программы от неосторожной установки в ней значения, которое находится вне границ массива stck.

Следующая программа демонстрирует улучшенный класс stack. Тут члены stck и tos недоступны.

 

class TestStack {

public static void main(String args[]) {

Stack mystack1 = new Stack();

Stack mystack2 = new Stack();

 

// поместить несколько чисел в стек

for(int i=0; i<10; i++) mystack1.push(i);

for(int i=10; i<20; i++) mystack2.push(i);

 

// вытолкнуть эти числа из стека

System.out.println("Стек в mystack1:");

for(int i=0; i<10; i++)

System.out.println(mystack1.pop()) ;

 

System.out.println("Стек в mystack2:");

for(int i=0; i<10; i++)

System.out.println(mystack2.pop()) ;

 

// эти операторы не верны

// mystackl.tos = -2;

// mystack2.stck[3] = 100;

}

}

 

Доступ к данным, определенным в классе со спецификатором private, выполняется только через методы этого класса. Но иногда (и по разным причинам) возникает необходимость обойти эти ограничения. Например, с помощью спецификатора общего доступа public можно расширить область действия переменных экземпляра за пределы своего класса и, следовательно, получить возможность работать с ними не только через их собственные методы. В большинстве реальных классов нужно разрешать операции на данных только через собственные методы. Управление доступом особенно важно, когда нужно пользоваться наследованием.

Статические элементы

Иногда возникает необходимость определить элемент (член) класса так, чтобы появилась возможность пользоваться им независимо от какого-либо объекта этого класса. Обычно к элементам класса нужно обращаться только через объект этого класса. Однако можно создать элемент для использования без ссылки на определенный объект. Чтобы это сделать, укажите в начале его объявления ключевое слово static. Когда элемент объявляется как static, к нему можно обращаться до того, как создаются какие-либо объекты его класса, и без ссылки на какой-либо объект. Статическими можно объявлять как методы, так и переменные. Наиболее общим примером static-элемента является метод main(). Он объявляется как static, потому что должен вызваться прежде, чем будут созданы какие-либо объекты.

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

Методы, объявленные как static имеют несколько ограничений:

- могут вызывать только другие static-методы;

- должны обращаться только к static-данным;

- никогда не могут ссылаться на this или super.

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

 

// Демонстрирует статические переменные, методы и блоки.

class UseStatic {

static int a = 3;

static int b;

static void meth(int x) {

System.out.println("x = " + x);

System.out.println("a = " + a);

System.out.println("b = " + b);

}

static {

System.out.println( "Статический блок инициализиройан. ");

b = a * 4;

}

public static void main(String args[]) {

meth(42);

}

}

 

Все static-инструкции выполняются сразу же после загрузки класса usestatic. Сначала, в a устанавливается 3, затем выполняется static-блок (печатая сообщения), и наконец, b инициализируется значением а*4 или 12. Затем вызывается метод main(), который обращается к meth(), передавая 42 параметру х. Три оператора println() обращаются к двум static-переменным (a и b) и к локальной переменной х.

Замечание. Внутри статического метода недопустимо обращаться к любым зкземплярным переменным.

Вывод этой программы:

 

Статический блок инициализиройан.

x = 42

a = 3

b = 12

 

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

 

classname.methodname()

 

Здесь classname — имя класса, в котором объявлен static-метод; methodname — имя статического метода. Нетрудно заметить, что этот формат подобен вызову нестатических методов через переменные, ссылающиеся на объект. К переменной static можно обращаться таким же образом — при помощи точечной операции с именем класса (в качестве левого операнда). Так Java реализует управляемую версию глобальных функций и глобальных переменных.

Например:

 

class StaticDemo {

static int a = 42;

static int b = 99;

static void callme() {

System.out.println("a = " + a);

}

}

 

class StaticByName {

public static void main(String args[]) {

StaticDemo.callme () ;

System.out.println("b = " + StaticDemo.b);

}

}

 

Здесь внутри метода main о к статическому методу caiimeo и статической переменной ь обращаются вне их класса.

Вывод этой программы:

a = 42

b = 99

 

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

1. Понятие инкапсуляции. Преимущества и недостатки.

2. Концепция класса.

3. Определение класса, общий вид и пример.

4. Где находится объявление класса и реализация методов. Отличия от других языков.

5. Отличие типа класса и объекта класса.

6. Обращение к полям и методам класса.

7. Имя класса и исходного файла, имя файла результата компиляции. Сходства и отличия.

8. Объявление и создание объектов типа класс. Примеры.

9. Операция динамического выделения, общий вид пример.

10. Работа со ссылками. Отличие от других языков.

11. Описание методов класса, общий вид и пример.

12. Оператор возвращения значения метода, общий вид и пример.

13. Аргументы и параметры, их понятия, сходство и отличия.

14. Конструкторы, общий вид и пример. Сущность понятия.

15. Обращение к объекту внутри объекта.

16. Сокрытие переменных области видимости.

17. Освобождение выделенных ресурсов.

18. Завершение работы класса. Освобождение ресурсов.

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

20. Цели сокрытия данных и методов.

 

 




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

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