$ sudo teach IT
МОДУЛЬ 7 · УРОК 6

Пакеты и организация кода

Когда классов становится сотни, пакеты поддерживают порядок. Пакеты — это папки для Java-классов, обеспечивающие пространства имён и структуру проекта.

~30 минут Для новичков Java ООП

Зачем нужны пакеты

Представьте библиотеку с тысячами книг на полках без порядка. Найти нужную книгу — кошмар. Пакеты — это как полки с подписями: «Романы», «Наука», «Детективы».

Пакет в Java решает две задачи:

  • Организация: классы группируются по функциональности (model, service, util). В большом проекте с сотнями классов без пакетов impossible найти нужный.
  • Пространство имён: классы с одинаковыми именами могут сосуществовать в разных пакетах. Например, java.util.Date и java.sql.Date — два разных класса Date.

Аналогия: два человека named «Александр Иванов». Без контекста — непонятно, о ком речь. Но если один — «Иванов из Москвы», а другой — «Иванов из Питера», всё ясно. Пакет — это как город для класса.

Объявление пакета

Первая строка в файле — объявление пакета. Путь должен совпадать со структурой папок:

// Файл: com/company/model/User.java
package com.company.model;

public class User {
    private String name;
    private String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

Структура папок должна точно соответствовать пакету:

src/
  main/
    java/
      com/
        company/
          model/
            User.java    ← package com.company.model
          service/
            UserService.java  ← package com.company.service
          util/
            StringUtils.java  ← package com.company.util

Если пакет не объявлен, класс находится в default-пакете. Это работает, но плохо — в реальных проектах всегда используйте пакеты.

Импорт классов

Чтобы использовать класс из другого пакета, нужно импортировать его через import:

// Файл: com/company/service/UserService.java
package com.company.service;

import com.company.model.User;
import java.util.List;
import java.util.ArrayList;

public class UserService {
    private List<User> users = new ArrayList<>();

    public void addUser(User user) {
        users.add(user);
    }

    public User findByName(String name) {
        for (User user : users) {
            if (user.getName().equals(name)) {
                return user;
            }
        }
        return null;
    }

    public void listAll() {
        for (User user : users) {
            System.out.println(user.getName() + " — " + user.getEmail());
        }
    }
}

Обратите внимание: сначала идёт package, потом import. Порядок важен!

Полный путь к файлу:

src/main/java/com/company/service/UserService.java

Полное квалифицированное имя (FQN)

FQN (Fully Qualified Name) — полное имя класса включая пакет: java.util.ArrayList, com.company.model.User.

// Без import — используйте FQN:
java.util.List<String> list = new java.util.ArrayList<>();
java.util.Date date = new java.util.Date();
java.io.File file = new java.io.File("test.txt");

// С import — гораздо чище:
import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import java.io.File;

List<String> list = new ArrayList<>();
Date date = new Date();
File file = new File("test.txt");

FQN полезен, когда нужно явно указать, какой класс имеется в виду (например, при конфликте имён):

import java.util.Date;
import java.sql.Date;

public class Example {
    java.util.Date utilDate = new java.util.Date();
    java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
}

Соглашения об именовании пакетов

Соглашения помогают другим разработчикам понимать структуру вашего проекта:

  • Обратный домен: com.company.project (вместо project.company.com)
  • Все буквы нижнего регистра: com.company.model (не com.company.Model)
  • Без специальных символов: только буквы, цифры и точки
  • Одно слово на уровень: com.company.project.module
// Типичная структура:
com.company.project
  ├── model         // Модели данных (User, Product, Order)
  ├── service       // Бизнес-логика (UserService, ProductService)
  ├── repository    // Работа с БД (UserRepository)
  ├── controller    // Обработка запросов (UserController)
  ├── util          // Утилиты (StringUtils, DateUtils)
  ├── config        // Конфигурация (AppConfig)
  └── exception     // Исключения (UserNotFoundException)

Типичная структура проекта

Вот как выглядит реальный Java-проект:

myapp/
  src/
    main/
      java/
        com/
          myapp/
            model/
              User.java
              Product.java
              Order.java
            service/
              UserService.java
              ProductService.java
              OrderService.java
            repository/
              UserRepository.java
              ProductRepository.java
            controller/
              UserController.java
              ProductController.java
            util/
              StringUtils.java
              DateUtils.java
            config/
              AppConfig.java
            exception/
              UserNotFoundException.java
              InvalidDataException.java
      resources/
        application.properties
    test/
      java/
        com/
          myapp/
            service/
              UserServiceTest.java

Каждый пакет имеет свою ответственность:

  • model — данные: классы User, Product, Order
  • service — бизнес-логика: обработка данных, вычисления
  • repository — работа с хранилищем (БД, файлы)
  • controller — обработка внешних запросов
  • util — общие утилиты: работа со строками, датами
  • config — настройки приложения
  • exception — пользовательские исключения

import static

import static импортирует статические成员, чтобы не писать имя класса:

// Без import static:
double x = Math.sqrt(16);
System.out.println(Math.PI);
System.out.println(Math.max(10, 20));

// С import static:
import static java.lang.Math.*;
import static java.lang.System.out;

public class Test {
    public static void main(String[] args) {
        double x = sqrt(16);
        out.println(PI);
        out.println(max(10, 20));
    }
}

import static java.lang.Math.* — импортирует ВСЕ статические成员 класса Math. Можно импортировать конкретный: import static java.lang.Math.PI;

Пакеты и модификаторы доступа

Пакеты тесно связаны с модификаторами доступа. Package-private доступ работает именно на уровне пакета:

// Файл: com/company/model/User.java
package com.company.model;

public class User {
    String name;         // package-private — доступен в com.company.model
    public String email; // public — доступен везде
}

// Файл: com/company/service/UserService.java
package com.company.service;

import com.company.model.User;

public class UserService {
    void process(User user) {
        // user.name = "Иван";  // Ошибка! name из другого пакета
        user.email = "i@mail.ru"; // OK — email public
    }
}

Если классы в разных пакетах, package-private члены недоступны. Для доступа нужен public или protected.

Полный пример: многофайловый проект

Давайте создадим простой проект «Библиотека» с несколькими пакетами:

// Файл: com/library/model/Book.java
package com.library.model;

public class Book {
    private String title;
    private String author;
    private boolean isAvailable;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
        this.isAvailable = true;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public boolean isAvailable() {
        return isAvailable;
    }

    public void setAvailable(boolean available) {
        isAvailable = available;
    }

    public void display() {
        System.out.printf("\"%s\" — %s [%s]%n", title, author,
            isAvailable ? "доступна" : "выдана");
    }
}
// Файл: com/library/service/Library.java
package com.library.service;

import com.library.model.Book;
import java.util.List;
import java.util.ArrayList;

public class Library {
    private String name;
    private List<Book> books;

    public Library(String name) {
        this.name = name;
        this.books = new ArrayList<>();
    }

    public void addBook(Book book) {
        books.add(book);
        System.out.println("Добавлена: " + book.getTitle());
    }

    public Book findBook(String title) {
        for (Book book : books) {
            if (book.getTitle().equalsIgnoreCase(title)) {
                return book;
            }
        }
        return null;
    }

    public boolean checkoutBook(String title) {
        Book book = findBook(title);
        if (book != null && book.isAvailable()) {
            book.setAvailable(false);
            System.out.println("Выдана: " + book.getTitle());
            return true;
        }
        System.out.println("Книга не найдена или уже выдана");
        return false;
    }

    public boolean returnBook(String title) {
        Book book = findBook(title);
        if (book != null && !book.isAvailable()) {
            book.setAvailable(true);
            System.out.println("Возвращена: " + book.getTitle());
            return true;
        }
        System.out.println("Книга не найдена или уже в библиотеке");
        return false;
    }

    public void listBooks() {
        System.out.println("=== " + name + " ===");
        for (Book book : books) {
            book.display();
        }
        System.out.println("Всего книг: " + books.size());
    }
}
// Файл: com/library/Main.java
package com.library;

import com.library.model.Book;
import com.library.service.Library;

public class Main {
    public static void main(String[] args) {
        Library lib = new Library("Городская библиотека");

        lib.addBook(new Book("Война и мир", "Толстой"));
        lib.addBook(new Book("Преступление и наказание", "Достоевский"));
        lib.addBook(new Book("Мастер и Маргарита", "Булгаков"));

        lib.listBooks();

        lib.checkoutBook("Война и мир");
        lib.listBooks();

        lib.returnBook("Война и мир");
        lib.listBooks();
    }
}

Wildcard-импорты

Вместо импорта каждого класса по отдельности можно импортировать все классы из пакета через *:

// По одному:
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

// Или всё сразу:
import java.util.*;

Wildcard-импорты удобны, но имеют недостатки:

  • Непонятно, какие именно классы используются
  • Может возникнуть конфликт имён
  • Немного замедляет компиляцию (но незначительно)

Рекомендация: импортируйте конкретные классы. Используйте * только для пакетов с множеством мелких классов (JUnit, Mockito).

Автоматический импорт

Некоторые пакеты импортируются автоматически:

  • java.lang.* — String, System, Math, Integer и др.
  • java.lang.Object — базовый класс всех объектов

Поэтому вы можете писать System.out.println() без импорта — класс System в пакете java.lang.

Правила организации

  • Один public-класс на файл
  • Имя файла = имя public-класса + .java
  • Пакет объявляется первой строкой
  • Потом идут import-ы
  • Потом класс
  • Порядок в файле: package → import → class
// Порядок:
package com.company.model;   // 1. Пакет

import java.util.List;       // 2. Импорты
import java.util.ArrayList;

public class User {          // 3. Класс
    // тело класса
}

Типичные ошибки

// Ошибка 1: package не совпадает с папкой
// Файл: User.java (в корне)
package com.company.model; // Ошибка! Папка не com/company/model

// Ошибка 2: забыли импорт
package com.company.service;
// import com.company.model.User; // Забыли!
public class UserService {
    User user; // Ошибка! User не импортирован
}

// Ошибка 3: неверный порядок
import java.util.List;       // Ошибка! Сначала package
package com.company.service; // package должен быть первым

// Правильно:
package com.company.service;
import java.util.List;

Итоги урока

  • Пакеты = папки для классов (организация + пространство имён).
  • package — первая строка в файле.
  • import импортирует классы из других пакетов.
  • FQN = полное имя класса включая пакет.
  • Соглашение: com.company.project.module.
  • import static импортирует статические成员.
  • Структура: model, service, repository, controller, util.
  • Порядок в файле: package → import → class.

Следующий модуль: Наследование и полиморфизм!

Тест: Пакеты и организация кода

10 вопросов

Пакеты

Premium