W3docs

Создание собственных пакетов в Java

Создавайте собственные пакеты Java: структура директорий, объявление package и компиляция.

Читать чужие пакеты — одно дело. Создать собственный — три небольших шага: выбрать имя, организовать директории и добавить нужное объявление package в начало каждого файла. Компилятор и JVM следуют строгим соглашениям, и после нескольких раз это становится привычкой. Если вы только знакомитесь с пакетами, начните с обзора пакетов Java; эта глава посвящена созданию собственных.

Три правила

Всё, что вы пишете внутри пакета, должно соблюдать ровно три правила:

  1. Первый не-комментарный оператор файла — это объявление package. Никакой другой оператор не может предшествовать ему.
  2. Путь к файлу отражает имя пакета. com.example.util.Strings должен находиться по пути com/example/util/Strings.java относительно какого-либо корня исходных файлов.
  3. Файл называется по имени публичного класса верхнего уровня. Strings.java для public class Strings.

Это всё. Остальное — соглашения.

Выбор имени пакета

Компилятор проверяет лишь три правила выше, но несколько соглашений об именовании помогают избежать коллизий и придерживаться идиоматического стиля:

  • Только строчные буквы. com.w3docs.greet, но не com.W3Docs.Greet. Прописные буквы в именах пакетов допустимы, но нарушают соглашение, которого ожидают все Java-разработчики, и создают проблемы на файловых системах, нечувствительных к регистру.
  • Используйте обратный порядок домена. Если вы владеете w3docs.com, ваши пакеты начинаются с com.w3docs. Это гарантирует глобальную уникальность, чтобы ваш Greeter не конфликтовал с чужим Greeter в classpath.
  • Без ключевых слов Java. Сегмент пакета не может быть зарезервированным словом. Если бы ваш домен был int.example.com, нельзя было бы написать com.example.int — пришлось бы экранировать, например com.example.int_.
  • Без ведущих цифр в сегменте. com.3m.tape недопустимо, потому что 3m — не валидный идентификатор; распространённое решение: com._3m.tape.

Если не объявить пакет вообще, классы попадут в пакет по умолчанию — подходит для разовых набросков, но классы из него нельзя импортировать в пакетный код, поэтому в реальных проектах избегайте этого.

Минимальный пример

Допустим, вы хотите создать небольшой утилитарный пакет com.w3docs.greet. Вот его структура:

project/
└── src/
    └── com/
        └── w3docs/
            └── greet/
                ├── Greeter.java
                ├── Greeting.java
                └── Main.java

Greeting.java:

package com.w3docs.greet;

public record Greeting(String language, String text) {}

Greeter.java:

package com.w3docs.greet;

public class Greeter {
  public Greeting greet(String name) {
    return new Greeting("en", "Hello, " + name + "!");
  }
}

Main.java:

package com.w3docs.greet;

public class Main {
  public static void main(String[] args) {
    System.out.println(new Greeter().greet("Ada").text());
  }
}

Все три файла начинаются с одной строки package com.w3docs.greet;. Поскольку они находятся в одном пакете, Greeter и Main могут использовать Greeting без import — импортировать нужно только типы из других пакетов.

Компиляция и запуск из командной строки

Из корня project/ стандартные команды выглядят так:

# Compile every .java file under src/ into a parallel tree under out/
javac -d out $(find src -name "*.java")

# Run a main class by its fully-qualified name, telling the JVM where out/ is.
java -cp out com.w3docs.greet.Main

-d out указывает javac, куда помещать скомпилированные файлы .class; при этом структура директорий пакета сохраняется. -cp out (classpath) — способ, которым java находит их во время выполнения. Подробнее об этом рассказывает следующая глава про classpath в Java.

Если объявление пакета не совпадает с расположением файла относительно корня исходных файлов, javac принимает его (пакет определяется объявлением, а не путём) — но java потерпит неудачу во время выполнения с ошибкой NoClassDefFoundError. Всегда держите их в соответствии.

Подпакеты не являются вложенными

Может показаться, что com.example «содержит» com.example.util, но для Java-компилятора это два несвязанных пакета, которые случайно имеют общий префикс имени. Класс в com.example не имеет особого доступа к членам с пакетной видимостью из com.example.util. Наследования пакетов не существует.

Что подпакеты дают — это дерево директорий, удобное для навигации, и логичное место для группировки связанного кода. В большинстве реальных проектов подпакеты организованы по функциональным областям (auth, billing, parser) или по слоям (controller, service, repository) — но ни тот, ни другой подход не даёт подпакету дополнительного доступа.

Единицы компиляции и package-info.java

Файл .java также называется единицей компиляции и может содержать:

  • Одно объявление package (или ни одного — для пакета по умолчанию).
  • Любое количество объявлений import.
  • Любое количество объявлений типов, из которых не более одного может быть public, и оно должно совпадать с именем файла.

Существует также специальный файл package-info.java, единственная задача которого — хранить Javadoc и аннотации уровня пакета:

/**
 * Greeting utilities for the W3Docs Java book.
 */
@NullMarked
package com.w3docs.greet;

Никаких типов — только комментарий, необязательные аннотации и строка package. Такой файл встречается в хорошо задокументированных библиотеках.

Рабочий пример

Эта программа объявляет два типа, которые в реальном проекте находились бы в одном пакете, и использует их вместе. Исполняемый виджет здесь сглаживает структуру файлов, чтобы вы видели всю схему в одном листинге, но в реальном проекте каждый публичный тип жил бы в своём собственном файле .java в структуре директорий, показанной выше.

java— editable, runs on the server

Что дальше

Пользовательские пакеты охватывают код, который вы пишете сами. Однако большую часть времени вы будете опираться на пакеты JDK — классы стандартной библиотеки для коллекций, ввода-вывода, работы со временем и так далее. Следующая глава — это обзор тех из них, с которыми вы будете работать чаще всего. Перейдите к встроенным пакетам Java.

Практика

Практика
В исходном файле Java объявлен пакет `package com.example.util;`, но после компиляции файл `.class` находится по пути `out/com/example/Strings.class`. Что произойдёт?
В исходном файле Java объявлен пакет `package com.example.util;`, но после компиляции файл `.class` находится по пути `out/com/example/Strings.class`. Что произойдёт?
Was this page helpful?