Пакеты в Java служат механизмом инкапсуляции, который используется для объединения связанных классов, интерфейсов и подпакетов.
На рис. 4.3 показан пример иерархии пакета, содержащий пакет, названный wizard
, который содержит два других пакета: pandorasBox
и spells
. Пакет pandorasBox
содержит класс, названный Clown
, который реализует интерфейс, названный Magic
, тоже расположенный в этом же пакете. Кроме того, пакет pandorasBox
содержит класс с именем LovePotion
и подпакет, названный artifacts
, содержащий класс Ailment
. В пакете spells
находятся два класса: Baldness
и LovePotion
. Класс Baldness
является подклассом класса Ailment
, расположенного в подпакете artifacts
пакета PandorasBox
.
Для уникальной идентификации членов пакета в пакетной иерархии используется нотация точки (.
). Класс wizard.pandorasBox.LovePotion
отличается от класса wizard.spells.LovePotion
. Класс Ailment
может быть просто обозначен при помощи имени wizard.pandorasBox.artifacts.Ailment
. Это называется полным составным именем члена пакета. Неудивительно, что большая часть программных сред Java отображает полные составные имена пакетов на конструкции, лежащей в основе (иерархической) файловой системы. Например, на Unix-системах файл класса LovePotion.class
соответствует классу wizard.pandorasBox.LovePotion
, который можно найти в директории wizard/pandorasBox
.
В глобальной схеме именования предполагается использовать доменные имена Интернета, чтобы обеспечить уникальную идентификацию пакета. Если бы вышеприведенный пакет wizard
был реализован компанией Sorcerers Limited, чье собственное доменное имя sorcerersltd.com
, то его полное составное имя могло бы быть:
com.sorcerersltd.wizard
Подпакет wizard.pandorasBox.artifacts
можно разместить где-то в другом месте, лишь бы его можно было уникально идентифицировать. Подпакет не оказывает влияния на доступность членов. Подпакеты большей частью служат как организующая конструкция, а не функция языка.
Иерархия пакетов представляет организацию классов и интерфейсов Java. Она не представляет организацию исходного кода классов и интерфейсов. Исходный код не представляет важности в этом аспекте. Каждый файл исходного кода Java (также называемый модулем компиляции (compilation unit) может содержать ноль или более определений классов и интерфейсов, но компилятор создает отдельный файл класса (class), содержащий байт-код Java для каждого из них. Класс или интерфейс может указать, что его байт-код Java находится в отдельном пакете, при помощи объявления package
.
Оператор package
имеет следующий синтаксис:
package <полное составное имя пакета>;
Максимум одно объявление пакета может присутствовать в исходном файле, и оно должно быть первым оператором в модуле. Имя пакета сохраняется в байт-коде Java для типов, содержащихся в этом пакете.
Обратите внимание, что такая схема приводит к двум результатам. Первый — все классы и интерфейсы в файле исходного кода размещаются в одном и том же пакете. Второй — некоторые файлы исходного кода могут использоваться для задания содержимого пакета.
Если объявление package
опущено в модуле компиляции, то байт-код Java для модуля компиляции будет принадлежать неименованному пакету, который обычно является синонимом текущей рабочей директории в хост-системе.
В (примере 4.7) проиллюстрировано, как пакет wizard.pandorasBox
из рис. 4.3 может быть задан в объявлении package
.
Защита типов (классов или интерфейсов) в пакете может воспрепятствовать доступу к пакету извне. Пусть задан ссылочный тип, который доступен извне пакета, к нему можно получить доступ двумя способами. В первом способе используется полное составное имя типа. Однако написание длинных имен может стать утомительным. Второй способ использует объявление import
, чтобы обеспечить краткое обозначение задаваемого имени типа. Объявление import
должно быть первым оператором после объявления пакета в исходном файле. Простая форма объявления import
имеет следующий синтаксис:
import <полное составное имя типа>;
Такое объявление называется импортом отдельного типа. Как и предполагается из названия, такое объявление import
обеспечивает краткое обозначение для одного класса или интерфейса. Простое имя типа (т.е. его идентификатор) может использоваться для доступа к этому частному типу. В данном ниже объявлении import
:
import wizard.pandorasBox.Clown;
имя Clown
может использоваться в файле исходного кода для того, чтобы ссылаться на этот класс.
Может использоваться альтернативная форма объявления import
:
import <полное составное имя пакета>.*;
Такой способ называется импортом типа по требованию. Он позволяет обратиться к любому типу, определенному в пакете, по его простому имени.
Объявление import
не вызывает рекурсивного импорта подпакетов. Оно не приводит в результате к включению исходного кода типов. Объявление только импортирует имена типов (т.е. делает имя типа доступным в коде модуля компиляции).
Все модули компиляции неявно импортируют пакет java.lang
. По этой причине вы можете обращаться к классу String
по его простому имени, а не использовать его полное составное имя java.lang.String
каждый раз.
Обратите внимание на класс Baldness
в файле Baldness.java
. Этот класс зависит от двух классов, имеющих одинаковое простое имя LovePotion
, но расположенных в разных пакетах: wizard.pandorasBox
и wizard.spells
соответственно. Для того чтобы различать два класса, можно использовать их полные составные имена. Однако, так как один из них находится в том же самом пакете, что и класс Baldness
, будет достаточно полностью квалифицировать только класс из другого пакета. Такое решение используется в приведенном коде. Такие конфликты имен обычно разрешаются использованием разных вариантов объявления import
вместе с полными составными именами.
// File: Baldness.java
package wizard.spells; // (1) Объявление пакета
// ...
import wizard.pandorasBox.artifacts.*; // (3) Импорт подпакета
// ...
public class Baldness extends Ailment { // (4) Сокращенное имя для Ailment
wizard.pandorasBox.LovePotion tlcOne; // (5) Полное составное имя
LovePotion tlcTwo; // (6) Класс в этом же пакете
// ...
}
// ...
Класс Baldness
расширяет класс Ailment
, который находится в подпакете artifacts
пакета wizard.pandorasBox
. Новое объявление import
в строке (3) используется для импорта типов из подпакета artifacts
.
В следующем примере показано, как импорт отдельного типа можно использовать для разрешения неоднозначности имени класса, когда обращение к классу по простому имени неоднозначно. Следующее объявление import
допускает использование простого имени List
в качестве краткого имени для класса java.awt.List
:
import java.awt.*; // Импортирует все имена классов из java.awt
Добавьте следующие два объявления import
:
import java.awt.*; // Импортирует все имена классов из java.awt
import java.util.*; // Импортирует все имена классов из java.util
простое имя List
неоднозначно, так как ему соответствуют классы java.util.List
и java.awt.List
.
Добавление еще одного явного объявления import
для класса java.awt.List
разрешает проблему использования простого имени List
в качестве обозначения для этого класса.
import java.awt.*; // Импортирует все имена классов из java.awt
import java.util.*; // Импортирует все имена классов из java.util
import java.awt.List; // Импортирует имя класса List из java.awt
Как упоминалось выше, иерархия пакетов может быть отображена на иерархию в файловой системе. Можно думать об имени пакета как о пути в файловой системе. Обращаясь к (примеру 4.7), имя пакета wizard.pandorasBox
соответствует пути wizard/pandorasBox
. Компилятор javac может разместить байт-код в директории, которая соответствует объявлению package
в модуле компиляции. Байт-код Java для всех классов (и интерфейсов), определенных в файлах исходного кода Clown.java
и LovePotion.java
, будет размещен в директории wizard/pandorasBox
, так как в этих файлах исходного кода есть следующее объявление package
:
package wizard.pandorasBox;
Абсолютный путь директории wizard/pandorasBox
задается на этапе компиляции ключом -d
(d
— destination (назначение). Предположим, что текущей является директория /pgjc/work
и все файлы исходного кода могут быть найдены здесь, тогда команда
>javac -d . Clown.java Ailment.java Baldness.java
вызванная из рабочей директории, создаст директорию ./wizard/pandorasBox
(и любые другие требуемые поддиректории) в текущей директории и разместит байт-код java для всех классов и интерфейсов в директориях, соответствующих именам пакетов. Точка (.
) после ключа -d
означает текущую директорию. После компиляции кода из примера 4.7 приведенной выше командой иерархия файлов в директории /pgjc/work
будет отражать иерархию, как на рис. 4.3. Поведение же компилятора javac
по умолчанию (без ключа -d
) приведет к размещению всех файлов class
в текущей директории, а не в соответствующих поддиректориях.
Как мы выполняем программу? Так как текущей директорией является /pgjс/work
, а мы хотим выполнить Clown.class
, то потребуется определить полное составное имя класса Clown
в команде java.
>java wizard.pandorasBox.Clown
Это вызовет загрузку класса Clown
из файла байт-кода ./wizard/pandorasBox/Clown.class
и запустит на выполнение его метод main()
.
В документации средств разработки Java 2 SDK объясняется, как более тщательно составить схему организации пакетов. В частности, переменная окружения CLASSPATH
может использоваться для задания многочисленных путей, которые должны использовать утилиты Java для поиска на этапе загрузки классов и ресурсов.
Утилита JAR (Java ARchive - архив Java) обеспечивает удобный способ упаковки и размещения программ Java. Файл JAR создается инструментом jar. В обычном файл JAR-приложения содержатся class-файлы и дополнительные ресурсы, необходимые приложению (например, видео- и звуковые файлы). Кроме этого, создается специальный файл манифеста и добавляется в архив. Файл манифеста содержит описательную информацию — например, какой класс содержит метод main()
для запуска приложения.
Команда jar
имеет много опций (сродни команде tar
из Unix). Синтаксис для создания файла JAR для приложения (например, для примера 4.7) имеет следующий вид:
>jar cmf whereismain.txt bundledApo.jar wizard
Ключ c
говорит утилите jar, что надо создать архив. Ключ m
используется для создания и включения в архив файла манифеста. Информация, которая будет включена в файл манифеста, поступает из текстового файла, определенного в командной строке (whereismain.txt
). Ключ f
задает имя архива, который будет создан (bundledApp.jar
). Имя файла JAR может быть любым допустимым именем файла. Файлы, включаемые в архив, перечисляются в командной строке далее после имени файла JAR. В командной строке примера выше будет добавлено в архив содержимое директории wizard
. Если порядок ключей m
и f
поменять местами в командной строке, то порядок соответствующих им имен файлов должен быть также изменен.
Информация, включенная в файл манифеста, задается парами имя-значение. В примере 4.7 выполнение программы должно начинаться с запуска метода main()
класса wizard.pandorasBox.Clown
. В файле whereismain.txt
есть единственная запись:
Main-Class: wizard.pandorasBox.Clown
Значение предопределенного заголовка Main-Class
задает точку входа в приложение. Чтобы утилита jar надлежащим образом обрабатывала последнюю текстовую строку файла, она должна быть завершена символом перевода строки. Это также справедливо и в случае, когда в файле только одна строка.
Приложение из архива можно выполнить следующей командой:
>java -jar bundledApp.jar
Программные аргументы можно задать после имени файла JAR.
Правила видимости | Модификаторы доступа для классов и интерфейсов верхнего уровня |