1. Exportação de pacotes: quem vê seu código?
No mundo pré-Java 9 tudo era simples — só que não muito seguro. Se uma classe fosse public, qualquer um poderia vê-la tendo acesso ao JAR ou ao classpath. Isso levava ao uso acidental ou intencional de classes internas e até pacotes inteiros fora do seu projeto. Sim, dava para esconder algo com private ou package-private, mas se a classe é public, ela fica acessível a todos.
Com a chegada dos módulos isso mudou. Agora, mesmo que a classe seja declarada como public, não é possível usá-la de outro módulo se o pacote em que ela está não for exportado explicitamente via module-info.java.
Como isso funciona na prática
Suponha que você tenha a seguinte estrutura de projeto:
my-app/
src/
core/
com/example/core/
CoreService.java
InternalHelper.java
module-info.java
app/
com/example/app/
Main.java
module-info.java
No arquivo core/module-info.java você escreve:
module core {
exports com.example.core;
}
Então apenas as classes do pacote com.example.core estarão disponíveis para outros módulos. Tudo o que estiver em outros pacotes (por exemplo, com.example.core.internal) ficará invisível — mesmo que haja classes public lá. Isso é o encapsulamento modular.
Mini demo: “visível/invisível”
Dentro de core:
// com/example/core/CoreService.java
package com.example.core;
public class CoreService {
public void doWork() {
System.out.println("Work done!");
}
}
Dentro de 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();
}
}
Se você remover a linha exports com.example.core; de core/module-info.java, na compilação você receberá o erro:
error: package com.example.core is not visible
Mesmo que CoreService seja public!
2. Importação de dependências: requires e isolamento rigoroso
No JPMS não dá para simplesmente usar uma classe de outro módulo. É preciso declarar explicitamente a dependência com requires.
Exemplo
No arquivo app/module-info.java:
module app {
requires core;
}
Agora o módulo app pode usar tudo o que o módulo core exporta.
Se você esquecer de escrever requires core;, receberá um erro de compilação:
error: package com.example.core is not visible
ou
error: cannot access CoreService
Ponto importante
- requires atua no nível de módulos, não de pacotes.
- Você não pode “importar” apenas um pacote — apenas todo o módulo exportado.
3. Comparação: módulos vs. public/private
Os módulos adicionam um novo nível de encapsulamento, que fica “acima” de classes e pacotes.
| Nível | O que regula? | Como funciona? |
|---|---|---|
| private | Acesso dentro da classe | Apenas dentro do próprio arquivo |
| package-private (padrão) | Acesso dentro do pacote | Todas as classes no mesmo pacote |
| public | Acesso para todos | Qualquer código em qualquer lugar |
| module | Acesso entre módulos | Somente pacotes exportados |
Ideia principal:
- Uma classe pode ser public, mas, se o seu pacote não for exportado, ela fica acessível apenas dentro do módulo.
- Exportar um pacote com exports é como uma “janela” pela qual seu código é visível para outros módulos.
Exemplo: implementação ocultada
// com/example/core/internal/SecretSauce.java
package com.example.core.internal;
public class SecretSauce {
public void addMagic() {}
}
Se você NÃO exportar o pacote com.example.core.internal no module-info.java, nenhum módulo externo conseguirá usar essa classe, mesmo que ela seja public!
4. Exemplo: core e app — API e implementação
Vamos ver um cenário típico: o módulo core fornece o API, o módulo app — o utiliza.
Estrutura:
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; // Apenas o API!
// Não exportamos 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; // Isso causará um erro de compilação!
public class Main {
public static void main(String[] args) {
// CoreImpl impl = new CoreImpl(); // Erro! O pacote não é exportado.
// Você só pode usar o que é visível pelo API.
}
}
Resultado:
- O módulo app vê apenas o que core exporta.
- A implementação (impl) fica oculta, mesmo que as classes lá sejam public.
5. Prática: brincando com export e visibilidade
Passo 1: remover o export
Em core/module-info.java comente a linha:
// exports com.example.core;
Agora tente compilar o projeto. Espere um erro de compilação no módulo app — ele não verá as classes de com.example.core.
Passo 2: exportar somente o API
Restaure a linha exports com.example.core;, mas não exporte com.example.core.impl. Tente, no módulo app, importar uma classe de impl. Você novamente receberá um erro de compilação — está tudo correto!
Passo 3: classe public em um pacote não exportado
Crie uma classe public em um pacote não exportado. Tente usá-la de outro módulo — não vai funcionar. Isso é o encapsulamento modular em ação.
6. Como isso aparece na IDE e “em termos simples”
- Ao tentar importar uma classe de um pacote não exportado, a IDE destacará um erro.
- A dica explicará que o pacote não é exportado pelo módulo.
- Adicione exports para o pacote necessário — o erro desaparecerá.
Esquema: níveis de acesso
+---------------------+
| MODUL’ |
| (module-info.java) |
+---------------------+
|
v
+---------------------+
| PAKETY |
| (package, export) |
+---------------------+
|
v
+---------------------+
| KLASSY |
| (public/private) |
+---------------------+
|
v
+---------------------+
| METODY/POLYA |
| (public/private) |
+---------------------+
7. Nuances e particularidades importantes
Exportação “apenas para amigos”: exports ... to
Às vezes é necessário exportar um pacote apenas para módulos específicos (por exemplo, para testes ou extensões especiais):
exports com.example.core.internal to my.special.module, my.test.module;
Agora somente os módulos indicados verão esse pacote.
É possível exportar vários pacotes?
Sim! Basta adicionar novas linhas de exports:
exports com.example.core;
exports com.example.core.api;
É possível exportar “tudo”?
Não. No sistema de módulos do Java você sempre especifica explicitamente o que exportar. Essa é a sua força.
8. Erros típicos ao trabalhar com encapsulamento modular
Erro nº 1: Esperar que uma classe public esteja sempre visível.
Se o pacote não for exportado via module-info.java, a classe ficará invisível para outros módulos, mesmo que ela seja public. Esse é o “tropeço” mais comum para iniciantes.
Erro nº 2: Esquecer de declarar requires.
Se o seu módulo usa classes de outro módulo, mas não declarou requires, você terá um erro de compilação. Não se esqueça de descrever explicitamente as dependências.
Erro nº 3: Tentar exportar o mesmo pacote a partir de dois módulos.
No JPMS cada pacote pode ser exportado por apenas um módulo. Se você violar essa regra, o compilador vai barrar.
Erro nº 4: Estrutura incorreta — module-info.java no lugar errado.
O arquivo module-info.java deve estar na raiz do código-fonte do módulo; caso contrário, o módulo não será reconhecido.
Erro nº 5: Dependências cíclicas entre módulos.
Se o módulo A requer B e B requer A — você terá um erro. Evite ciclos no grafo de dependências.
GO TO FULL VERSION