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. Практика: граємося з exports і видимістю

Крок 1: видаляємо exports

У 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 — отримаєте помилку. Уникайте циклів у графі залежностей.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ