CodeGym /Cursos /JAVA 25 SELF /Introdução aos módulos: por que eles são necessários

Introdução aos módulos: por que eles são necessários

JAVA 25 SELF
Nível 60 , Lição 0
Disponível

1. Problemas da abordagem antiga

“Classpath party”: quando todos na mesma cozinha

Antes do surgimento dos módulos no Java (na versão 9), todo o código do aplicativo, bibliotecas e dependências caía em um grande monte — o classpath. Praticamente não havia limites entre bibliotecas: qualquer classe que entrasse no classpath podia ser encontrada e usada por qualquer um.

Surgiam conflitos de nomes: duas bibliotecas contêm uma classe com o mesmo nome totalmente qualificado, por exemplo com.example.Util — “alguma delas” era escolhida, e você só descobria o problema pelo comportamento estranho em tempo de execução.

Clássico do gênero — dependency hell: uma biblioteca exige um logger versão 1.2, outra — 1.3, e no classpath acabam ficando ambas. A JVM não sabe qual carregar, e você encontra um enigmático NoSuchMethodError.

E mais: mesmo que uma classe seja declarada como public, mas destinada ao uso interno da biblioteca, outro código ainda pode usá-la — e acabar quebrando algo no processo. Projetos grandes viravam um campo minado, e a manutenção — uma aventura.

2. O que é um módulo no Java?

Módulo: como uma caixa com orifícios

No Java 9 surgiu o sistema de módulos (JPMS). Um módulo é uma caixa com classes e pacotes na qual você mesmo faz “orifícios”: indica explicitamente o que é exposto para fora (exports) e o que fica escondido dentro. E sim: mesmo uma classe public não fica visível para outros módulos se o seu pacote não for exportado.

Definição formal

Um módulo é uma unidade lógica de organização de código que reúne pacotes e classes relacionados. Cada módulo declara explicitamente:

  • o que ele exporta — torna disponível para outros módulos (exports);
  • e o que ele importa — de quais outros módulos depende (requires).

Propriedades-chave do módulo:

  • Fronteiras explícitas. Fica claramente definido o que está acessível de fora e o que não está.
  • Dependências explícitas. Um módulo não “enxerga” outro módulo sem um requires explícito.
  • Isolamento. Detalhes internos podem ser totalmente ocultados do mundo externo.

3. Vantagens da modularidade

Encapsulamento aprimorado

Surgiu um novo nível de visibilidade — no nível do módulo. Mesmo que a classe seja public, se o seu pacote não for exportado, ela só é visível dentro do módulo. O interior da biblioteca não “vaza” mais para fora.

Declaração explícita de dependências

As dependências necessárias são listadas em module-info.java. O compilador e a IDE avisam com antecedência se você esquecer de declarar um módulo do qual depende.

Segurança e confiabilidade aprimoradas

Restringir o acesso a APIs internas dificulta a interferência acidental ou intencional. Isso é crítico para grandes bibliotecas e módulos de plataforma.

Possibilidade de criar uma JRE “enxuta”

Com o jlink é possível montar um runtime mínimo apenas com os módulos necessários. Em nuvem e cenários embedded isso economiza espaço em disco e memória.

Bônus: inicialização mais rápida e menor tamanho

Não são carregados todos os módulos, mas apenas os realmente usados — os aplicativos iniciam mais rápido e consomem menos recursos.

4. Onde os módulos são usados

Na biblioteca padrão do Java

A partir do Java 9, a própria plataforma é modular. Exemplos:

  • java.base — módulo básico (obrigatório para qualquer programa).
  • java.sql — trabalho com bancos de dados.
  • java.xml — trabalho com XML.

Se você não usa XML, o módulo java.xml nem entrará no seu runtime.

Em aplicativos e bibliotecas grandes

Must-have para sistemas corporativos, onde dezenas de equipes desenvolvem partes de um monorepositório. A modularidade reduz conflitos e simplifica a manutenção.

Em projetos próprios

Mesmo em um pet project, os módulos ajudam a treinar o pensamento arquitetural e manter o código em ordem, evitando “espaguete”.

5. Resumo rápido da sintaxe: module-info.java

O mais importante — o arquivo module-info.java

O arquivo module-info.java fica na raiz dos fontes do módulo e declara seus limites e dependências.

module my.awesome.module {
    exports com.example.api;        // Pacote exportado
    requires java.sql;              // Dependência de um módulo padrão
}

Principais palavras-chave:

  • module <nome> — declara um módulo.
  • exports <pacote> — torna um pacote disponível para outros módulos.
  • requires <módulo> — declara dependência de outro módulo.

Exemplo mínimo:

module com.myproject.core {
    exports com.myproject.core.api;
}

Exemplo com dependência:

module com.myproject.app {
    requires com.myproject.core;
    requires java.sql;
}

Recursos adicionais (breve): para reflexão existe opens, para serviços — uses e provides ... with ... (detalhes — em aulas avançadas).

6. Dicas úteis

Módulos são como “passaportes” para classes. Antes, qualquer classe com o visto public podia “viajar” pelo aplicativo. Agora, também é preciso o “passaporte” modular — a exportação do pacote (exports). Sem ela, a classe fica “em casa”.

Dependency hell é um termo real. Se você já viu ClassNotFoundException: com.google.common.base.Strings, você já esteve lá. Os módulos foram feitos para espantar esses “demônios” com fronteiras e dependências estritas.

Todo o Java agora é modular. Mesmo que você não escreva seus próprios módulos, a plataforma já está dividida em dezenas. Tente o comando:

java --list-modules

Como isso impacta o desenvolvimento

Você gerencia explicitamente a visibilidade do código, e os pacotes internos deixam de ficar acessíveis a todos “por acaso”. A IDE e o compilador capturam erros mais cedo: esqueceu de declarar uma dependência — o projeto não compila. A manutenção de sistemas grandes fica mais simples: é mais fácil entender quem depende de quem e o que pode quebrar com mudanças.

7. Erros comuns ao migrar para módulos

Erro nº 1: exportou o pacote? A classe é public, mas... Você declarou uma classe como public, porém não exportou seu pacote — outros módulos não conseguirão usá-la. O compilador informará: “O pacote não é exportado pelo módulo”.

Erro nº 2: esqueceu de declarar a dependência com requires. Você usa um tipo de outro módulo, mas esqueceu de adicionar requires no module-info.java. Resultado — erro de compilação: “o módulo não consegue encontrar a classe necessária”.

Erro nº 3: nomes de módulos duplicados. Em um projeto grande, dois módulos acidentalmente receberam o mesmo nome. A JVM não perdoa isso — renomeie e adote um padrão de nomenclatura consistente.

Erro nº 4: esquecer dos módulos padrão. Por exemplo, você usa JDBC, mas não adicionou requires java.sql;. No Java 8 “já era visível”, mas no 9+ — não.

Erro nº 5: tentativa de usar uma classe interna de outro módulo. Se o pacote não é exportado, mesmo uma classe public permanece invisível de fora. Exporte o pacote ou mova o API para uma camada “pública”.

1
Tarefa
JAVA 25 SELF, nível 60, lição 0
Bloqueado
Coração mínimo do módulo 🫀
Coração mínimo do módulo 🫀
1
Tarefa
JAVA 25 SELF, nível 60, lição 0
Bloqueado
Abrindo o gateway para a biblioteca 🔑
Abrindo o gateway para a biblioteca 🔑
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION