24

24


•        Предоставить «методы доступа», то есть методы для чтения и модифика­ции значения. С точки зрения ООП этот подход является предпочтитель­ным, и именно он используется в технологии JavaBeans.



public



При использовании ключевого слова public вы фактически объявляете, что сле­дующее за ним объявление члена класса доступно для всех, и прежде всего для клиентских программистов, использующих библиотеку. Предположим, вы оп­ределили пакет dessert, содержащий следующий компилируемый модуль:






// access/dessert/Cookie.java 11 Создание библиотеки, package access.dessert.



public class Cookie { public CookieO {



System.out.println("Конструктор Cookie");



}



void biteO { System out printlnCbite"); } } /// ~



Помните, что файл Cookie.java должен располагаться в подкаталоге dessert каталога с именем access (соответствующем данной главе книги), а последний должен быть включен в переменную CLASSPATH. Не стоит полагать, будто Java всегда начинает поиск с текущего каталога. Если вы не укажете символ . (точка) в переменной окружения CLASSPATH в качестве одного из путей по­иска, то Java и не заглянет в текущий каталог.



Если теперь написать программу, использующую класс Cookie:



// access/Dinner java // Использование библиотеки import access.dessert *;



public class Dinner {



public static void main(Stnng[] args) { Cookie x = new CookieO: //! x.biteO; // Обращение невозможно



}



} /* Output: Конструктор Cookie */// ~



то можно создать объект Cookie, поскольку конструктор этого класса объявлен открытым (public) и сам класс также объявлен как public. (Понятие открытого класса мы позднее рассмотрим чуть подробнее.) Тем не менее метод bite() этого класса недоступен в файле Dinner.java, поскольку доступ к нему предоставляет­ся только в пакете dessert. Так компилятор предотвращает неправильное ис­пользование методов.



Пакет по умолчанию



С другой стороны, следующий код работает, хотя на первый взгляд он вроде бы нарушает правила:



//• access/Cake java



// Обращение к классу из другого компилируемого модуля



class Cake {



public static void mainCString[] args) { Pie x = new PieO, x f():



}



} /* Output. Pie f() */// ~



Второй файл в том же каталоге:



//• access/Pie.java



// Другой класс



class Pie { void f() { System.out.pnntlnC'Pie.fO"): }



} ///:-



Вроде бы эти два файла не имеют ничего общего, и все же в классе Cake мож­но создать объект Pie и вызвать его метод f()! (Чтобы файлы компилировались, переменная CLASSPATH должна содержать символ точки.) Естественно было бы предположить, что класс Pie и метод f() имеют доступ в пределах пакета и поэтому закрыты для Cake. Онидействительно обладают доступом в преде­лах пакета —здесь все верно. Однако их доступность в классе Cake.java объяс­няется тем, что они находятся в одном каталоге и не имеют явно заданного име­ни пакета. Java по умолчанию включает такие файлы в «пакет по умолчанию» для текущего каталога, поэтому они обладают доступом в пределах пакета к дру­гим файлам в этом каталоге.



private



Ключевое слово private означает, что доступ к члену класса не предоставляется никому, кроме методов этого класса. Другие классы того же пакета также не мо­гут обращаться к private-членам. На первый взгляд вы вроде бы изолируете класс даже от самого себя. С другой стороны, вполне вероятно, что пакет соз­дается целой группой разработчиков; в этом случае private позволяет изме­нять члены класса, не опасаясь, что это отразится на другом классе данного пакета.



Предлагаемый по умолчанию доступ в пределах пакета часто оказывается достаточен для сокрытия данных; напомню, что такой член класса недоступен пользователю пакета. Это удобно, так как обычно используется именно такой уровень доступа (даже в том случае, когда вы просто забудете добавить специ­фикатор доступа). Таким образом, доступ public чаще всего используется тогда, когда вы хотите сделать какие-либо члены класса доступными для программи- ста-клиента. Может показаться, что спецификатор доступа private применяется редко и можно обойтись и без него. Однако разумное применение private очень важно, особенно в условиях многопоточного программирования (см. далее).



Пример использования private:



II- access/IceCream.java



// Демонстрация ключевого слова private.



class Sundae {



private SundaeO {} static Sundae makeASundaeO { return new SundaeO;



}



}



public class IceCream {



public static void main(String[] args) { III Sundae x = new SundaeO; Sundae x = Sundae makeASundae();



}



} ///-



Перед вами пример ситуации, в которой private может быть очень полезен: предположим, вы хотите контролировать процесс создания объекта, не разре­шая посторонним вызывать конкретный конструктор (или любые конструкто­ры). В данном примере запрещается создавать объекты Sundae с помощью конст­руктора; вместо этого пользователь должен использовать метод makeASundae().



Все «вспомогательные» методы классов стоит объявить как private, чтобы предотвратить их случайные вызовы в пакете; тем самым вы фактически запре­щаете изменение поведения метода или его удаление.



То же верно и к private-полям внутри класса. Если только вы не собираетесь предоставить доступ пользователям к внутренней реализации (а это происхо­дит гораздо реже, чем можно себе представить), объявляйте все поля своих классов со спецификатором private.



protected



Чтобы понять смысл спецификатора доступа protected, необходимо немного за­бежать вперед. Сразу скажу, что понимание этого раздела не обязательно до знакомства с наследованием (глава 7). И все же для получения цельного пред­ставления здесь приводится описание protected и примеры его использования.



Ключевое слово protected тесно связано с понятиемнаследования, при кото­ром к уже существующему классу (называемомубазовым классом)добавляются новые члены, причем исходная реализация остается неизменной. Также можно изменять поведение уже существующих членов класса. Для создания нового класса на базе существующего используется ключевое слово extends:



class Foo extends Bar {



Остальная часть реализации выглядит как обычно.



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



Интерфейс и реализация



Контроль над доступом часто называютсокрытием реализации.Помещение дан­ных и методов в классы в комбинации с сокрытием реализации часто называютинкапсуляцией.В результате появляется тип данных, обладающий характери­стиками и поведением.



Доступ к типам данных ограничивается по двум причинам. Первая причи­на — чтобы программисту-клиенту знать, что он может использовать, а что не может. Вы вольны встроить в структуру реализации свои внутренние меха­низмы, не опасаясь того, что программисты-клиенты по случайности использу­ют их в качестве части интерфейса.



Это подводит нас непосредственно ко второй причине — разделению интер­фейса и реализации. Если в программе использована определенная структура, но программисты-клиенты не могут получить доступ к ее членам, кроме от­правки сообщений риЬНс-интерфейсу, вы можете изменять все, что не объявле­но как public (члены с доступом в пределах пакета, protected и private), не нару­шая работоспособности изменений клиентского кода.



Для большей ясности при написании классов можно использовать такой стиль: сначала записываются открытые члены (public), затем следуют защищен­ные члены (protected), потом — с доступом в пределах пакета и наконец закры­тые члены (private). Преимущество такой схемы состоит в том, что при чтении исходного текста пользователь сначала видит то, что ему важно (открытые чле­ны, доступ к которым можно получить отовсюду), а затем останавливается при переходе к закрытым членам, являющимся частью внутренней реализации:



//. access/OrganizedByAccess.java public class OrganizedByAccess {



public void publO {



/*



*/ }



public void pub2() {



/*



*/ }



public void pub3() {



/*



. */ }



private void privlO



{ /* ■



*/



private void priv20



{ /* •



*/



private void priv30



{ /*



*/



private int i;









// ..} III -Такой подход лишь частично упрощает чтение кода, поскольку интерфейс и реализация все еще совмещены. Иначе говоря, вы все еще видите исходный код — реализацию — так, как он записан прямо в классе. Вдобавок документа­ция в комментариях, создаваемая с помощью javadoc, снижает необходимость в чтении исходного текста программистом-клиентом.



Доступ к классам



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






Для управления доступом к классу, спецификатор доступа записывается пе­ред ключевым словом class:



public class Widget {



Если ваша библиотека называется, например, access, то любой програм­мист-клиент сумеет обратиться извне к классу Widget:



import access Widget: или



import access *;



Впрочем, при этом действуют некоторые ограничения:



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



•        Имя открытого класса должно в точности совпадать с именем файла, в котором содержится компилируемый модуль, включая регистр симво­лов. Поэтому для класса Widget имя файла должно быть Widget.java, но никак не widget.java или WIDGET.java. В противном случае вы снова по­лучите сообщение об ошибке.



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



Допустим, в пакете access имеется класс, который всего лишь выполняет не­которые служебные операции для класса Widget или для любого другого ри- ЬИс-класса пакета. Конечно, вам не хочется возиться с созданием лишней доку­ментации для клиента; возможно, когда-нибудь вы просто измените структуру пакета, уберете этот вспомогательный класс и добавите новую реализацию. Но для этого нужно точно знать, что ни один программист-клиент не зависит от конкретной реализации библиотеки. Для этого вы просто опускаете ключе­вое слово public в определении класса; ведь в таком случае он ограничивается пакетным доступом, то есть может использоваться только в пределах своего пакета.



При создании класса с доступом в пределах пакета его поля все равно реко­мендуется помечать как private (всегда нужно по максимуму перекрывать дос­туп к полям класса), но методам стоит давать тот же уровень доступа, что имеет и сам класс (в пределах пакета). Класс с пакетным доступом обычно использу­ется только в своем пакете, и делать методы такого класса открытыми (public) стоит только при крайней необходимости — а о таких случаях вам сообщит компилятор.



Заметьте, что класс нельзя объявить как private (что сделает класс недоступ­ным для окружающих, использовать он сможет только «сам себя») или protected
[2]
. Поэтому у вас есть лишь такой выбор при задании доступа к классу: в пределах пакета или открытый (public). Если вы хотите перекрыть доступ к классу для всех, объявите все его конструкторы со спецификатором private, соответственно, запретив кому бы то ни было создание объектов этого класса. Только вы сами, в статическом методе своего класса, сможете создавать такие объекты. Пример: