Пакеты Java
Группируйте классы Java в пакеты, соблюдайте соглашения об именовании и структурируйте проекты для удобного сопровождения.
В Java нет единого пространства имён для всех классов. Каждый класс находится внутри пакета — именованного пространства, которое служит одновременно единицей организации и пространством имён на уровне языка. Два класса с именем Logger могут сосуществовать без конфликтов, если они находятся в разных пакетах, а имя пакета используется повсеместно: в операторах import, в полностью квалифицированных именах, в файловой системе и даже в манифестах JAR. Понимание пакетов позволяет с первого взгляда разобраться в структуре чужого проекта.
На этой странице рассматривается, что такое пакет, как его именовать, особый случай пакета по умолчанию, как имена пакетов соотносятся с каталогами и как объявить пакет в собственном исходном файле.
Что такое пакет
Пакет выполняет три функции одновременно:
- Пространство имён.
java.util.Dateиjava.sql.Date— разные классы; имя пакета разделяет их. - Граница доступа. Без модификатора члены класса видны только внутри того же пакета — уровень доступа «package-private». Это реальная, структурная форма инкапсуляции; см. Модификаторы доступа.
- Каталог. Имя пакета однозначно соответствует пути к папке.
com.example.app.utilнаходится по путиcom/example/app/util/.
Одно и то же имя используется в трёх местах — в объявлении, пути к файлу и операторе import — и все три должны совпадать.
Соглашения об именовании
В Java принято именование в обратном порядке DNS на основе домена, которым вы владеете:
- Только строчные буквы:
com.example, а неCom.Example. - Обратный порядок домена: проект на
w3docs.comиспользуетcom.w3docsв качестве корня. - Далее следуют сегменты проекта, модуля и функциональности:
com.w3docs.learnjava.parser. - Избегайте зарезервированных слов Java в качестве сегментов (
int,class,new). Если ваш домен содержит одно из них, измените его:com.example.int_или разбейте иначе.
Эти соглашения важны не только для эстетики. Именно правило обратного DNS позволяет безопасно добавлять JAR от разных организаций в один classpath без конфликтов имён.
Пакет по умолчанию
Файл .java без объявления package принадлежит пакету по умолчанию (безымянному). Из этого следует два последствия:
- Нельзя использовать
importиз пакета по умолчанию в именованный пакет. Всё, что находится в нём, фактически отрезано от реального кода. - Инструменты сборки, IDE и модульные системы рассматривают пакет по умолчанию как вырожденный случай — многие из них просто отказываются компилировать против него.
Используйте его для разовых файлов типа Hello.java. Не выпускайте из него ничего в продакшн.
Как пакеты соотносятся с каталогами
Если вы объявляете package com.w3docs.learnjava.parser; в начале файла Tokenizer.java, этот файл должен находиться по пути:
com/w3docs/learnjava/parser/Tokenizer.javaотносительно корня исходного кода. Компилятор не выводит пакет из пути — он читает объявление и доверяет вам. Однако среда выполнения (и большинство инструментов) будет недовольна, если они не совпадают.
Этот корень исходного кода — начало кроличьей норы classpath: JVM должна знать, где начинается дерево пакетов, иначе она ничего не найдёт.
Объявление пакета
Оператор package присваивает класс пакету. Он должен быть самым первым оператором в файле — перед любым import, перед самим классом. Перед ним могут стоять только комментарии и пустые строки.
// File: com/w3docs/learnjava/parser/Tokenizer.java
package com.w3docs.learnjava.parser;
import java.util.List;
public class Tokenizer {
public List<String> tokenize(String source) {
// ...
return List.of();
}
}Отныне этот класс известен везде под полностью квалифицированным именем com.w3docs.learnjava.parser.Tokenizer. Код в том же пакете может обращаться к нему просто как Tokenizer; код из других пакетов либо импортирует его, либо указывает полное имя.
package, и только один публичный класс верхнего уровня — имя которого должно совпадать с именем файла. Поместите package в любое место, кроме начала файла, и компилятор отклонит его.Практический пример: два Loggerа
Самый наглядный аргумент в пользу пакетов — конфликты, которые они предотвращают. Следующая программа использует два класса с именем Logger по духу — java.util.logging.Logger из JDK по полностью квалифицированному имени — и показывает, как пакет класса становится частью его идентификации во время выполнения.
Два вывода из запуска программы: классы знают свой пакет во время выполнения (через Class.getName() и Class.getPackage()), а полностью квалифицированное имя — это то, что однозначно идентифицирует тип: Logger сам по себе неоднозначен, а java.util.logging.Logger — нет.
Что дальше
Назвать пакет — это одно; подключить его типы в свой код — совсем другое. Следующая глава посвящена оператору import — импорту отдельных типов, импорту с подстановочными символами, статическому импорту и тому, когда каждый из них уместен.