23

23


II: access/ImportedMyClass.java
import access.mypackage.*:
public class ImportedMyClass {
public static void main(String[] args) { MyClass m = new MyClassO:
}
} ///
Ключевые слова package и import позволяют разработчику библиотеки орга­низовать логическое деление глобального пространства имен, предотвращаю­щее конфликты имен независимо от того, сколько людей подключится к Ин­тернету и начнет писать свои классы на Java.
Создание уникальных имен пакетов
Вы можете заметить, что, поскольку пакет на самом деле никогда не «упаковы­вается» в единый файл, он может состоять из множества файлов .class, что спо­собно привести к беспорядку, может, даже хаосу. Для предотвращения пробле­мы логично было бы разместить все файлы .class конкретного пакета в одном каталоге, то есть воспользоваться иерархической структурой файловой систе­мы. Это первый способ решения проблемы нагромождения файлов в Java; о втором вы узнаете при описании утилиты jar.
Размещение файлов пакета в отдельном каталоге решает две другие задачи: создание уникальных имен пакетов и обнаружение классов, потерянных в «деб­рях» структуры каталогов. Как было упомянуто в главе 2, проблема решается «кодированием» пути файла в имени пакета. По общепринятой схеме первая часть имени пакета должна состоять из перевернутого доменного имени разра­ботчика класса. Так как доменные имена Интернета уникальны, соблюдение этого правила обеспечит уникальность имен пакетов и предотвратит конфликты. (Только если ваше доменное имя не достанется кому-то другому, кто начнет писать программы на Java под тем же именем.) Конечно, если у вас нет собст­венного доменного имени, для создания уникальных имен пакетов придется придумать комбинацию с малой вероятностью повторения (скажем, имя и фа­милия). Если же вы решите публиковать свои программы на Java, стоит немно­го потратиться на получение собственного доменного имени.
Вторая составляющая — преобразование имени пакета в каталог на диске компьютера. Если программе во время исполнения понадобится загрузить файл .class (что делается динамически, в точке, где программа создает объект определенного класса, или при запросе доступа к статическим членам класса), она может найти каталог, в котором располагается файл .class.
Интерпретатор Java действует по следующей схеме. Сначала он проверяет переменную окружения CLASSPATH (ее значение задается операционной сис­темой, а иногда программой установки Java или инструментарием Java). CLASSPATH содержит список из одного или нескольких каталогов, используе­мых в качестве корневых при поиске файлов .class. Начиная с этих корневых каталогов, интерпретатор берет имя пакета и заменяет точки на слеши для по­лучения полного пути (таким образом, директива package foo.bar.baz преобразу­ется в foo\bar\baz, foo/bar/baz или что-то еще в зависимости от вашей операци­онной системы). Затем полученное имя присоединяется к различным эле­ментам CLASSPATH. В указанных местах ведется поиск файлов .class, имена которых совпадают с именем создаваемого программой класса. (Поиск также ведется в стандартных каталогах, определяемых местонахождением интерпре­татора Java.)
Чтобы понять все сказанное, рассмотрим мое доменное имя: MindView.net. Обращая его, получаем уникальное глобальное имя для моих классов: net. mindview. (Расширения com, edu, org и другие в пакетах Java прежде записыва­лись в верхнем регистре, но начиная с версии Java 2 имена пакетов записывают­ся только строчными буквами.) Если потребуется создать библиотеку с именем simple, я получаю следующее имя пакета:
package net.mindview.simple,
Теперь полученное имя пакета можно использовать в качестве объединяю­щего пространства имен для следующих двух файлов:
//: net/mindview/simple/Vector.java
// Создание пакета
package net mindview simple,
public class Vector { public VectorО {
System out printlnCnet mindview.simple.Vector");
}
} ///:-
Как упоминалось ранее, директива package должна находиться в первой строке исходного кода. Второй файл выглядит почти так же:
//: net/mindview/simple/List java
// Создание пакета
package net.mindview simple;
public class List { public ListO {

System.out pri ntln("net.mi ndvi ew.si mple.Li st"),
}
} /// ~
В моей системе оба файла находятся в следующем подкаталоге:
С \DOC\JavaT\net\mindview\simple
Если вы посмотрите на файлы, то увидите имя пакета net.mindview.simple, но что с первой частью пути? О ней позаботится переменная окружения CLAS- SPATH, которая на моей машине выглядит следующим образом:
CLASSPATH= ,D \JAVA\LIB,C.\D0C\JavaT
Как видите, CLASSPATH может содержать несколько альтернативных пу­тей для поиска.
Однако для файлов JAR используется другой подход. Вы должны записать имя файла JAR в переменной CLASSPATH, не ограничиваясь указанием пути к месту его расположения. Таким образом, для файла JAR с именем grape.jar пе­ременная окружения должна выглядеть так:
CLASSPATH= .D.\JAVAXLIB.С \flavors\grape.jar
После настройки CLASSPATH следующий файл можно разместить в любом каталоге:
// access/LibTest.java // Uses the library, import net mindview simple *;
public class LibTest {
public static void main(String[] args) { Vector v = new VectorO. List 1 = new ListO:
}
} /* Output:
net.mindview simple Vector net mindview.simple List */// -
Когда компилятор встречает директиву import для библиотеки simple, он на­чинает поиск в каталогах, перечисленных в переменной CLASSPATH, найдет каталог net/mindview/simple, а затем переходит к поиску компилированных фай­лов с подходящими именами (Vector.class для класса Vector и List.class для клас­са List). Заметьте, что как классы, так и необходимые методы классов Vector и List должны быть объявлены со спецификатором public.
Конфликты имен
Что происходит при импортировании конструкцией * двух библиотек, имею­щих в своем составе идентичные имена? Предположим, программа содержит следующие директивы:
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 можно не обращать внимания).

Report Page