JavaRush /Курсы /JAVA 25 SELF /Инкапсуляция через модули: экспорт и импорт

Инкапсуляция через модули: экспорт и импорт

JAVA 25 SELF
60 уровень , 2 лекция
Открыта

1. Экспорт пакетов: кто видит ваш код?

В мире до Java 9 всё было просто. Только вот не очень безопасно. Если класс был public, его мог видеть кто угодно при наличии доступа к JAR или classpath. Это приводило к тому, что внутренние классы и даже целые пакеты могли случайно или намеренно использоваться вне вашего проекта. Да, можно было спрятать что-то с помощью private или package-private, но если класс public — он доступен всем.

С появлением модулей это изменилось. Теперь даже если класс объявлен как public, его нельзя использовать из другого модуля, если пакет, в котором он лежит, не экспортирован явно через module-info.java.

Как это выглядит на практике

Допустим, у вас есть структура проекта:

my-app/
  src/
    core/
      com/example/core/
        CoreService.java
        InternalHelper.java
      module-info.java
    app/
      com/example/app/
        Main.java
      module-info.java

В файле core/module-info.java вы пишете:

module core {
    exports com.example.core;
}

Тогда только классы из пакета com.example.core будут доступны другим модулям. Всё, что находится в других пакетах (например, com.example.core.internal), будет невидимо — даже если там есть public-классы. Это и есть модульная инкапсуляция.

Мини-демо: «видно/невидимо»

Внутри core:

// com/example/core/CoreService.java
package com.example.core;

public class CoreService {
    public void doWork() {
        System.out.println("Work done!");
    }
}

Внутри app:

// com/example/app/Main.java
package com.example.app;

import com.example.core.CoreService;

public class Main {
    public static void main(String[] args) {
        CoreService service = new CoreService();
        service.doWork();
    }
}

Если вы уберёте строку exports com.example.core; из core/module-info.java, то при компиляции получите ошибку:

error: package com.example.core is not visible

Даже если CoreServicepublic!

2. Импорт зависимостей: requires и строгая изоляция

В JPMS нельзя просто так взять и использовать класс из другого модуля. Нужно явно объявить зависимость через requires.

Пример

В файле app/module-info.java:

module app {
    requires core;
}

Теперь модуль app может использовать всё, что экспортирует модуль core.

Если вы забудете написать requires core;, получите ошибку компиляции:

error: package com.example.core is not visible

или

error: cannot access CoreService

Важный момент

  • requires работает на уровне модулей, а не пакетов.
  • Вы не можете «импортировать» только один пакет — только весь экспортируемый модуль.

3. Сравнение: модули против public/private

Модули добавляют новый уровень инкапсуляции, который находится «над» классами и пакетами.

Уровень Что регулирует? Как работает?
private Доступ внутри класса Только внутри одного файла
package-private (по умолчанию) Доступ внутри пакета Все классы в одном пакете
public Доступ для всех Любой код в любом месте
module Доступ между модулями Только экспортированные пакеты

Ключевая идея:

  • Класс может быть public, но если его пакет не экспортирован, он доступен только внутри модуля.
  • Экспорт пакета через exports — как «окно», через которое виден ваш код другим модулям.

Пример: спрятанная реализация

// com/example/core/internal/SecretSauce.java
package com.example.core.internal;

public class SecretSauce {
    public void addMagic() {}
}

Если вы НЕ экспортируете пакет com.example.core.internal в module-info.java, то ни один внешний модуль не сможет использовать этот класс, даже если он public!

4. Пример: core и app — API и реализация

Рассмотрим типовой сценарий: модуль core предоставляет API, модуль app — использует его.

Структура:

core/
  com/example/core/
    CoreAPI.java
  com/example/core/impl/
    CoreImpl.java
  module-info.java

app/
  com/example/app/
    Main.java
  module-info.java

core/module-info.java:

module core {
    exports com.example.core; // Только API!
    // Не экспортируем com.example.core.impl
}

app/module-info.java:

module app {
    requires core;
}

com/example/core/CoreAPI.java:

package com.example.core;

public interface CoreAPI {
    void doSomething();
}

com/example/core/impl/CoreImpl.java:

package com.example.core.impl;

import com.example.core.CoreAPI;

public class CoreImpl implements CoreAPI {
    @Override
    public void doSomething() {
        System.out.println("Doing something!");
    }
}

com/example/app/Main.java:

package com.example.app;

import com.example.core.CoreAPI;
// import com.example.core.impl.CoreImpl; // Это вызовет ошибку компиляции!

public class Main {
    public static void main(String[] args) {
        // CoreImpl impl = new CoreImpl(); // Ошибка! Пакет не экспортируется.
        // Можно использовать только то, что видно через API.
    }
}

Результат:

  • Модуль app видит только то, что экспортирует core.
  • Реализация (impl) спрятана, даже если классы там — public.

5. Практика: играем с export и видимостью

Шаг 1: удаляем export

В core/module-info.java закомментируйте строку:

// exports com.example.core;

Теперь попробуйте скомпилировать проект. Ожидайте ошибку компиляции в модуле app — он не увидит классы из com.example.core.

Шаг 2: экспортируем только API

Верните строку exports com.example.core;, но не экспортируйте com.example.core.impl. Попробуйте в модуле app импортировать класс из impl. Снова получите ошибку компиляции — всё честно!

Шаг 3: public-класс в неэкспортируемом пакете

Создайте public-класс в неэкспортируемом пакете. Попробуйте использовать его из другого модуля — не получится. Это и есть модульная инкапсуляция в действии.

6. Как это выглядит в IDE и «на пальцах»

  • При попытке импортировать класс из неэкспортируемого пакета IDE подсветит ошибку.
  • Подсказка объяснит, что пакет не экспортируется модулем.
  • Добавьте exports для нужного пакета — ошибка исчезнет.

Схема: уровни доступа

+---------------------+
|      МОДУЛЬ         |
|  (module-info.java) |
+---------------------+
       |
       v
+---------------------+
|     ПАКЕТЫ          |
| (package, export)   |
+---------------------+
       |
       v
+---------------------+
|     КЛАССЫ          |
| (public/private)    |
+---------------------+
       |
       v
+---------------------+
|    МЕТОДЫ/ПОЛЯ      |
| (public/private)    |
+---------------------+

7. Важные нюансы и особенности

Экспорт «только для друзей»: exports ... to

Иногда нужно экспортировать пакет только для определённых модулей (например, для тестов или специальных расширений):

exports com.example.core.internal to my.special.module, my.test.module;

Теперь только указанные модули увидят этот пакет.

Можно ли экспортировать несколько пакетов?

Да! Просто добавляйте новые строки exports:

exports com.example.core;
exports com.example.core.api;

Можно ли экспортировать «всё»?

Нет. В модульной системе Java вы всегда явно указываете, что экспортировать. Это и есть её сила.

8. Типичные ошибки при работе с модульной инкапсуляцией

Ошибка №1: Ожидание, что public-класс всегда виден.
Если пакет не экспортирован через module-info.java, класс будет невидим для других модулей, даже если он public. Это самая частая «ловушка» для новичков.

Ошибка №2: Забыли объявить requires.
Если ваш модуль использует классы из другого модуля, но не написал requires, получите ошибку компиляции. Не забывайте явно описывать зависимости.

Ошибка №3: Попытка экспортировать один и тот же пакет из двух модулей.
В JPMS каждый пакет может быть экспортирован только одним модулем. Если нарушить это правило, компилятор вас остановит.

Ошибка №4: Нарушение структуры — module-info.java не в том месте.
Файл module-info.java должен лежать в корне исходников модуля, иначе модуль не будет распознаваться.

Ошибка №5: Циклические зависимости между модулями.
Если модуль A требует B, а B требует A — получите ошибку. Избегайте циклов в графе зависимостей.

1
Задача
JAVA 25 SELF, 60 уровень, 2 лекция
Недоступна
Мастерская цифровых инструментов и демонстрация их силы 🛠️
Мастерская цифровых инструментов и демонстрация их силы 🛠️
1
Задача
JAVA 25 SELF, 60 уровень, 2 лекция
Недоступна
Хранитель секретов сервиса 🤫
Хранитель секретов сервиса 🤫
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ