Материал предоставлен https://it.rfei.ru

Пакеты

Пакеты в Java служат механизмом инкапсуляции, который используется для объединения связанных классов, интерфейсов и подпакетов.

На рис. 4.3 показан пример иерархии пакета, содержащий пакет, названный wizard, который содержит два других пакета: pandorasBox и spells. Пакет pandorasBox содержит класс, названный Clown, который реализует интерфейс, названный Magic, тоже расположенный в этом же пакете. Кроме того, пакет pandorasBox содержит класс с именем LovePotion и подпакет, названный artifacts, содержащий класс Ailment. В пакете spells находятся два класса: Baldness и LovePotion. Класс Baldness является подклассом класса Ailment, расположенного в подпакете artifacts пакета PandorasBox.

Рис. 4.3. Иерархия пакета

Для уникальной идентификации членов пакета в пакетной иерархии используется нотация точки (.). Класс 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

Утилита 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.

Правила видимостиМодификаторы доступа для классов и интерфейсов верхнего уровня