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 в «публичный» слой.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ