27

27


Конфликты имен
Что происходит при импортировании конструкцией * двух библиотек, имею­щих в своем составе идентичные имена? Предположим, программа содержит следующие директивы:
import net mindview simple *; import java.util.*,
Так как пакет java,util.* тоже содержит класс с именем Vector, это может при­вести к потенциальному конфликту. Но, пока вы не начнете писать код, вызы­вающий конфликты, все будет в порядке — и это хорошо, поскольку иначе вам пришлось бы тратить лишние усилия на предотвращение конфликтов, которых на самом деле нет.
Конфликтдействительнопроизойдет при попытке создать Vector:
Vector v = new VectorO;
К какому из классов Vector относится эта команда? Этого не знают ни ком­пилятор, ни читатель программы. Поэтому компилятор выдаст сообщение об ошибке и заставит явно указать нужное имя. Например, если мне понадобит­ся стандартный класс Java с именем Vector, я должен явно указать этот факт:
java util.Vector v = new java util,VectorO;
Данная команда (вместе с переменной окружения CLASSPATH) полностью описывает местоположение конкретного класса Vector, поэтому директива im­port java.util.* становится избыточной (по крайней мере, если вам не потребуют­ся другие классы из этого пакета).
Пользовательские библиотеки
Полученные знания позволяют вам создавать собственные библиотеки, сокра­щающие или полностью исключающие дублирование кода. Для примера можно взять уже знакомый псевдоним для метода System.out.println(), сокращающий количество вводимых символов. Его можно включить в класс Print:
//. net/mi ndvi ew/uti1/Print.java
// Методы-печати, которые могут использоваться
// без спецификаторов, благодаря конструкции
// Java SE5 static import.
package net.mindview.util;
import java.io *;
public class Print {
// Печать с переводом строки: public static void print(Object obj) { System.out.println(obj);
}
// Перевод строки: public static void print(S) { System, out. pri ntlnO;
}
// Печать без перевода строки public static void printnb(Object obj) { System.out.print(obj);
}
// Новая конструкция Java SE5 printfO (из языка С): public static PrintStream printf(String format, Object... args) {
return System.out.printf(format, args):
}
} ///-
Новые методы могут использоваться для вывода любых данных с новой строки (print()) или в текущей строке (printnb()).
Как нетрудно предположить, файл должен располагаться в одном из катало­гов, указанных в переменной окружения CLASSPATH, по пути net/mindview. После компиляции методы static print() и printnb() могут использоваться где угодно, для чего в программу достаточно включить директиву import static:
//: access/PrintTest.java
// Использование статических методов печати из Print.java. import static net.mindview.util.Print.*:
public class PrintTest {
public static void main(String[] args) {
print("Теперь это стало возможно!"): print(lOO): print(lOOL), print(3.14159):
}
} /* Output:
Теперь это стало возможно! 100 100
3.14159
Теперь, когда бы вы ни придумали новый интересный инструмент, вы всегда можете добавить его в свою библиотеку.
Предостережение при работе с пакетами
Помните, что создание пакета всегда неявно сопряжено с определением струк­туры каталогов. Пакетобязаннаходиться в одноименном каталоге, который, в свою очередь, определяется содержимым переменной CLASSPATH. Первые эксперименты с ключевым словом package могут оказаться неудачными, пока вы твердо не усвоите правило «имя пакета — его каталог». Иначе компилятор будет выводить множество сообщений о загадочных ошибках выполнения, о не­возможности найти класс, который находится рядом в этом же каталоге. Если у вас возникают такие ошибки, попробуйте закомментировать директиву package; если все запустится, вы знаете, где искать причины.
Спецификаторы доступа Java
В Java спецификаторы доступа public, protected и private располагаются перед определением членов классов — как полей, так и методов. Каждый специфика­тор доступа управляет только одним отдельным определением.
Если спецификатор доступа не указан, используется «пакетный» уровень доступа. Получается, что в любом случае действует та или иная категория дос­тупа. В нескольких ближайших подразделах описаны разные уровни доступа.
Доступ в пределах пакета
Во всех рассмотренных ранее примерах спецификаторы доступа не указыва­лись. Доступ по умолчанию не имеет ключевого слова, но часто его называют доступом в пределах пакета (package access, иногда «дружественным»). Это зна­чит, что член класса доступен для всех остальных классов текущего пакета, но для классов за пределами пакета он воспринимается как приватный (private). Так как компилируемый модуль — файл — может принадлежать лишь одному пакету, все классы одного компилируемого модуля автоматически открыты друг для друга в границах пакета.
Доступ в пределах пакета позволяет группировать взаимосвязанные классы в одном пакете, чтобы они могли легко взаимодействовать друг с другом. Раз­мещая классы в одном пакете, вы берете код пакета под полный контроль. Та­ким образом, только принадлежащий вам код будет обладать пакетным досту­пом к другому, принадлежащему вам же коду — и это вполне логично. Можно сказать, что доступ в пределах пакета и является основной причиной для груп­пировки классов в пакетах. Во многих языках определения в классах организу­ются совершенно произвольным образом, но в Java придется привыкать к более жесткой логике структуры. Вдобавок классы, которые не должны иметь доступ к классам текущего пакета, следует просто исключить из этого пакета.
Класс сам определяет, кому разрешен доступ к его членам. Не существует волшебного способа «ворваться» внутрь него. Код из другого пакета не может запросто обратиться к пакету и рассчитывать, что ему вдруг станут доступны все члены: protected, private и доступные в пакете. Получить доступ можно лишь несколькими «законными» способами:
•        Объявить член класса открытым (public), то есть доступным для кого угод­но и откуда угодно.
•        Сделать член доступным в пакете, не указывая другие спецификаторы доступа, и разместить другие классы в этом же пакете.
•        Как вы увидите в главе 7, где рассказывается о наследовании, производ­ный класс может получить доступ к защищенным (protected) членам ба­зового класса вместе с открытыми членами public (но не к приватцым членам private). Такой класс может пользоваться доступом в пределах па­кета только в том случае, если второй класс принадлежит тому же пакету (впрочем, пока на наследование и доступ protected можно не обращать внимания).
•        Предоставить «методы доступа», то есть методы для чтения и модифика­ции значения. С точки зрения ООП этот подход является предпочтитель­ным, и именно он используется в технологии 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();