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


Как вы уже знаете, инкапсуляция связывает данные с манипулирующим ими кодом. Однако инкапсуляция предоставляет еще один важный атрибут: управление доступом. Посредством инкапсуляции можно управлять тем, какие части программы могут получать доступ к членам класса. Управление доступом позволяет предотвращать злоупотребления. Например, предоставляя доступ к данным только посредством четко определенного набора методов, можно предотвратить злоупотребление этими данными. Таким образом, если класс реализован правильно, он создает "черный ящик", который можно использовать, но внутренний механизм которого защищен от повреждения. Однако представленные ранее классы не полностью соответствуют этой цели. Например, рассмотрим класс Stack, представленный в конце главы 6. Хотя методы push () и pop () действительно предоставляют управляемый интерфейс стека, этот интерфейс не обязателен для использования. То есть другая часть программы может обойти эти методы и обратиться к стеку непосредственно. Понятно, что в "плохих руках" эта возможность может приводить к проблемам. В этом разделе мы представим механизм, с помощью которого можно строго управлять доступом к различным членам класса.

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

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

Начнем с определения спецификаторов public и private. Когда член класса изменяется спецификатором доступа public, он становится доступным для любого другого кода. Когда член класса указан как private, он доступен только другим членам этого же класса. Теперь вам должно быть понятно, почему методу main () всегда предшествует спецификатор public. Этот метод вызывается кодом, расположенным вне данной программы — т.е. системой времени выполнения Java. При отсутствии спецификатора доступа по умолчанию член класса считается общедоступным внутри своего собственного пакета, но недоступным для кода, расположенного вне этого пакета. (Пакеты рассматриваются в следующей главе.)

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

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

public int i;
private double j ;
private int myMethod(int a, char, b) {
//...
}

Чтобы влияние использования общедоступного и приватного доступа было понятно, рассмотрим следующую программу:

/* Эта программа демонстрирует различие между
спецификаторами public и private. */ class Test {
int a; // доступ, определенный по умолчанию
public int b; // общедоступный доступ
private int с; // приватный доступ
// методы доступа к с
void setc(int i) {
// установка значения переменной с
с = i;
}
int getc() {
// получение значения переменной с
return с;
class AccessTest {
public static void main(String args[]) {
Test ob = new Test();
// Эти операторы правильны, а и b доступны непосредственно
ob.a = 10;
ob.b = 20;
// Этот оператор неверен и может вызвать ошибку
// ob.c = 100;
// Ошибка!
// Доступ к объекту с должен осуществляться посредством методов его класса
ob.setc(100) ;
// OK
System.out.println ("a, b, и с: " + ob.a + " " + ob.b + " " + ob.getcO);
}
}

Как видите, внутри класса Test использован метод доступа, заданный по умолчанию, что в данном примере равносильно указанию доступа public. Объект Ь явно указан как public. Объект с указан как приватный. Это означает, что он недоступен для кода, переделенного вне его класса. Поэтому внутри класса AccessTest объект с не может применяться непосредственно. Доступ к нему должен осуществляться посредством его общедоступных методов setc () и getc (). Удаление символа комментария из начала строки:

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

сделало бы компиляцию этой программы невозможной из-за нарушений правил доступа.

В качестве более реального примера применения управления доступа рассмотрим следующую усовершенствованную версию класса Stack, код которого был приведен в конце 6 главы:

// Этот класс определяет целочисленный стек, который может содержать 10 значений.
class Stack {
/* Теперь stck и tos являются приватными.
Это означает, что они не могут быть случайно или намеренно изменены так, чтобы повредить стек. */
private int stck[] = new int[10];
private int tos;
// Инициализация верхушки стека
Stack() {
tos = -1;
}
// Проталкивание элемента в стек
void push(int item) {
if(tos==9)
System.out.printIn("Стек полон.");
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 mystackl = new StackO;
Stack mystack2 = new StackO;
// проталкивание чисел в стек
for(int i=0; i<10; i++) mystackl.push (i);
for(int i=10; i<20; i++) mystack2.push(i);
// выталкивание этих чисел из стека
System.out.println ("Стек в mystackl:") ;
for(int i=0; i<10; i++)
System.out.printIn(mystackl.pop ());
System.out.println("Стек в mystack2:");
for(int i=0; i<10; i++)
System.out.println(mystack2.pop() ) ;
// эти операторы недопустимы
// mystackl.tos = -2;
// mystack2.stck[3] = 100;
}
}

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