Criando Pacotes Personalizados em Java
Crie seus próprios pacotes Java — estrutura de diretórios, declaração de pacote e como compilar e usá-los.
Ler os pacotes de outras pessoas é uma coisa. Criar os seus próprios requer três pequenos passos: escolher um nome, organizar o diretório e colocar a declaração package correta no topo de cada arquivo. O compilador e a JVM concordam com uma convenção rígida e, depois de fazer isso duas vezes, vira memória muscular. Se você é novo na ideia de pacotes, comece pela visão geral de pacotes Java primeiro; este capítulo foca em criar o seu próprio.
As três regras
Tudo que você escreve dentro de um pacote precisa obedecer exatamente três regras:
- A primeira instrução do arquivo que não seja comentário é a declaração
package. Nenhuma outra instrução pode precedê-la. - O caminho do arquivo espelha o nome do pacote.
com.example.util.Stringsdeve estar emcom/example/util/Strings.javadentro de alguma raiz de código-fonte. - O arquivo tem o nome da sua classe pública de nível superior.
Strings.javaparapublic class Strings.
É só isso. Todo o resto é convenção.
Escolhendo um nome de pacote
O compilador só aplica as três regras acima, mas algumas convenções de nomenclatura mantêm seus pacotes livres de colisões e idiomáticos:
- Tudo em minúsculas.
com.w3docs.greet, nuncacom.W3Docs.Greet. Letras maiúsculas em nomes de pacotes funcionam, mas quebram a convenção que todo desenvolvedor Java espera, e causam problemas em sistemas de arquivos que não diferenciam maiúsculas de minúsculas. - Inverta o nome do seu domínio. Se você possui
w3docs.com, seus pacotes começam comcom.w3docs. Isso garante unicidade global para que seuGreeternunca conflite com oGreeterde outra pessoa no classpath. - Sem palavras-chave Java. Um segmento de pacote não pode ser uma palavra reservada. Se seu domínio fosse
int.example.com, você não poderia escrevercom.example.int— você teria que escapá-lo, por exemplocom.example.int_. - Sem dígitos iniciais em um segmento.
com.3m.tapeé inválido porque3mnão é um identificador válido; uma correção comum écom._3m.tape.
Se você não declarar nenhum pacote, suas classes ficam no pacote padrão — adequado para um trecho descartável, mas classes ali não podem ser importadas por código com pacotes, então evite isso em projetos reais.
Um exemplo mínimo
Suponha que você quer um pequeno pacote utilitário chamado com.w3docs.greet. Aqui está a estrutura:
project/
└── src/
└── com/
└── w3docs/
└── greet/
├── Greeter.java
├── Greeting.java
└── Main.javaGreeting.java:
package com.w3docs.greet;
public record Greeting(String language, String text) {}Greeter.java:
package com.w3docs.greet;
public class Greeter {
public Greeting greet(String name) {
return new Greeting("en", "Hello, " + name + "!");
}
}Main.java:
package com.w3docs.greet;
public class Main {
public static void main(String[] args) {
System.out.println(new Greeter().greet("Ada").text());
}
}Os três arquivos começam com a mesma linha package com.w3docs.greet;. Como compartilham um pacote, Greeter e Main podem usar Greeting sem import — você só usa import para tipos de outros pacotes.
Compilando e executando pela linha de comando
A partir da raiz project/, os comandos canônicos são:
# Compile every .java file under src/ into a parallel tree under out/
javac -d out $(find src -name "*.java")
# Run a main class by its fully-qualified name, telling the JVM where out/ is.
java -cp out com.w3docs.greet.Main-d out diz ao javac onde colocar os arquivos .class compilados; ele preserva a estrutura de diretórios do pacote. -cp out (classpath) é como o java os encontra em tempo de execução — o próximo capítulo sobre o Java classpath entra nos detalhes.
Se a declaração do pacote não corresponder à localização do arquivo relativa à raiz do código-fonte, o javac aceita (o pacote é o que está declarado, não o que é inferido do caminho) — mas o java falhará em tempo de execução com NoClassDefFoundError. Mantenha sempre os dois em sincronia.
Sub-pacotes não são aninhados
É tentador pensar que com.example "contém" com.example.util, mas para o compilador Java eles são dois pacotes não relacionados que por acaso compartilham um prefixo de nome. Uma classe em com.example não tem acesso especial aos membros com visibilidade de pacote de com.example.util. Não existe herança de pacote.
O que sub-pacotes oferecem é uma árvore de diretórios fácil de navegar e um lugar lógico para agrupar código relacionado. A maioria dos projetos reais usa sub-pacotes por funcionalidade (auth, billing, parser) ou por camada (controller, service, repository) — mas nenhuma das escolhas dá ao sub-pacote qualquer acesso extra.
Unidades de compilação e package-info.java
Um arquivo .java também é chamado de unidade de compilação, e pode conter:
- Uma declaração
package(ou nenhuma, para o pacote padrão). - Qualquer número de declarações
import. - Qualquer número de declarações de tipo, das quais no máximo uma pode ser
public, e essa deve corresponder ao nome do arquivo.
Existe também um arquivo especial chamado package-info.java cujo único propósito é carregar Javadoc e anotações de nível de pacote:
/**
* Greeting utilities for the W3Docs Java book.
*/
@NullMarked
package com.w3docs.greet;Sem tipos — apenas o comentário, anotações opcionais e a linha package. Você verá isso em bibliotecas bem documentadas.
Um exemplo prático
Este programa declara dois tipos que viveriam no mesmo pacote em um projeto real e os usa juntos. O widget executável aqui achata a estrutura de arquivos para que você possa ver a ligação em uma única listagem de código-fonte, mas em um projeto real cada tipo público viveria em seu próprio arquivo .java dentro da estrutura de diretórios mostrada acima.
O que vem a seguir
Pacotes personalizados cobrem o código que você mesmo escreve. Na maioria das vezes, porém, você vai se apoiar nos pacotes do JDK — as classes da biblioteca padrão para coleções, E/S, tempo e muito mais. O próximo capítulo é um tour guiado pelos que você usará com mais frequência. Continue para pacotes integrados do Java.