1. Проблеми старого підходу
«Classpath party»: коли всі на одній кухні
До появи модулів у Java (у дев’ятій версії) увесь код застосунку, бібліотеки та залежності звалювалися в одну велику купу — classpath. Меж між бібліотеками фактично не було: будь-який клас, що потрапив до classpath, міг бути знайдений і використаний ким завгодно.
Виникали конфлікти імен: дві бібліотеки містили клас з однаковою повною назвою, наприклад com.example.Util — обирався «якийсь один», а ви дізнавалися про проблему вже за дивною поведінкою в рантаймі.
Класика жанру — dependency hell: одна бібліотека вимагає логер версії 1.2, інша — 1.3, а в classpath опиняються обидві. JVM не знає, що підхопити, і ви ловите загадкову NoSuchMethodError.
І ще: навіть якщо клас оголошено як public, але він призначений для внутрішніх потреб бібліотеки, інший код усе одно може його використати — і в процесі щось зламати. Великі проєкти перетворювалися на мінне поле, а супроводження — на пригоду.
2. Що таке модуль у Java?
Модуль: як коробка з дірочками
У Java 9 з’явилася модульна система (JPMS). Модуль — це коробка з класами та пакетами, у якій ви самі робите «дірочки»: явно зазначаєте, що показуєте назовні (exports), а що ховаєте всередині. Так, навіть public-клас не видно іншим модулям, якщо його пакет не експортовано.
Формальне визначення
Модуль — логічна одиниця поділу коду, що об’єднує пов’язані пакети та класи. Кожен модуль явно оголошує:
- що він експортує — робить доступним іншим модулям (exports);
- і що він імпортує — від яких інших модулів залежить (requires).
Ключові властивості модуля:
- Явні межі. Чітко визначено, що доступно ззовні, а що ні.
- Явні залежності. Модуль не «бачить» інший модуль без явного requires.
- Ізоляція. Внутрішні деталі можуть бути повністю приховані від зовнішнього світу.
3. Переваги модульності
Поліпшена інкапсуляція
З’явився новий рівень видимості — на рівні модуля. Навіть якщо клас public, але його пакет не експортується, він видимий лише всередині модуля. Внутрішні деталі бібліотеки більше не «просочуються» назовні.
Явний опис залежностей
Потрібні залежності перелічуються в module-info.java. Компілатор і IDE заздалегідь попередять, якщо ви забули вказати модуль, від якого залежите.
Поліпшена безпека та надійність
Обмеження доступу до внутрішніх API ускладнює випадкове або навмисне втручання. Це критично для великих бібліотек і платформних модулів.
Можливість створення «урізаних» JRE
За допомогою jlink можна зібрати мінімальний рантайм лише з потрібних модулів. У хмарі та embedded-сценаріях це заощаджує дисковий простір і пам’ять.
Бонус: прискорення старту та зменшення розміру
Завантажуються не всі модулі, а тільки реально використані — застосунки стартують швидше і споживають менше ресурсів.
4. Де використовуються модулі
У стандартній бібліотеці Java
Починаючи з Java 9, сама платформа модульна. Приклади:
- java.base — базовий модуль (обов’язковий для будь-якої програми).
- java.sql — робота з базами даних.
- java.xml — робота з XML.
Якщо ви не використовуєте XML, модуль java.xml навіть не потрапить у ваш рантайм.
У великих застосунках і бібліотеках
Must-have для корпоративних систем, де десятки команд розробляють частини монорепозиторію. Модульність зменшує конфлікти та спрощує супровід.
У власних проєктах
Навіть у pet-проєкті модулі допомагають тренувати архітектурне мислення й тримати код у порядку, уникаючи «спагеті».
5. Короткий огляд синтаксису: module-info.java
Найголовніше — файл module-info.java
Файл module-info.java лежить у корені вихідників модуля та оголошує його межі й залежності.
module my.awesome.module {
exports com.example.api; // Експортований пакет
requires java.sql; // Залежність від стандартного модуля
}
Основні ключові слова:
- module <назва> — оголошує модуль.
- exports <пакет> — робить пакет доступним іншим модулям.
- requires <модуль> — оголошує залежність від іншого модуля.
Мінімальний приклад:
module com.myproject.core {
exports com.myproject.core.api;
}
Приклад із залежністю:
module com.myproject.app {
requires com.myproject.core;
requires java.sql;
}
Додаткові можливості (коротко): для рефлексії є opens, для сервісів — uses і provides ... with ... (детальніше — у просунутих лекціях).
6. Корисні нюанси
Модулі — це як «паспорти» для класів. Раніше будь-який клас з візою public міг «подорожувати» застосунком. Тепер потрібен ще й модульний «паспорт» — експорт пакета (exports). Без нього клас залишається «вдома».
Dependency hell — це реальний термін. Якщо бачили ClassNotFoundException: com.google.common.base.Strings, ви там побували. Модулі покликані розігнати цих «чортів» суворими межами та залежностями.
Уся Java тепер модульна. Навіть якщо ви не пишете власні модулі, платформа вже розбита на десятки. Спробуйте команду:
java --list-modules
Як це впливає на розробку
Ви явно керуєте видимістю коду, і внутрішні пакети більше не стають доступними всім «випадково». IDE та компілятор ловлять помилки раніше: забули оголосити залежність — проєкт не збереться. Підтримка великих систем спрощується: легше зрозуміти, хто від кого залежить і що зламається при змінах.
7. Типові помилки під час переходу на модулі
Помилка № 1: забули експортувати пакет, але клас public. Ви оголосили клас як public, але не експортували його пакет — інші модулі не зможуть його використовувати. Компілатор повідомить: «Пакет не експортується модулем».
Помилка № 2: не оголосили залежність через requires. Ви використовуєте тип з іншого модуля, але забули додати requires у module-info.java. Підсумок — помилка компіляції «модуль не може знайти потрібний клас».
Помилка № 3: дубльовані імена модулів. У великому проєкті два модулі випадково отримали однакову назву. JVM такого не прощає — перейменуйте та дотримуйтеся єдиного найменування.
Помилка № 4: забули про стандартні модулі. Наприклад, використовуєте JDBC, але не додали requires java.sql;. У Java 8 «і так було видно», а у 9+ — ні.
Помилка № 5: спроба використати внутрішній клас іншого модуля. Якщо пакет не експортується, навіть public-клас залишається невидимим ззовні. Експортуйте пакет або винесіть API у «публічний» шар.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ