Paquetes en Java
Agrupa clases Java relacionadas en paquetes, sigue convenciones de nombres y estructura proyectos para facilitar el mantenimiento.
Java no tiene un gran depósito único de nombres de clases. Cada clase vive dentro de un paquete — un espacio con nombre que funciona tanto como unidad de organización como espacio de nombres a nivel Java. Dos clases llamadas Logger pueden coexistir sin colisión siempre que estén en paquetes diferentes, y el nombre del paquete se transmite en todas partes: en las declaraciones import, en los nombres completamente calificados, en el sistema de archivos e incluso en los manifiestos JAR. Conocer bien los paquetes es lo que te permite entender la estructura de un proyecto ajeno de un vistazo.
Esta página explica qué es un paquete, cómo nombrarlo, el caso especial del paquete predeterminado, cómo los nombres de paquetes se asignan a directorios y cómo declarar un paquete en tu propio archivo fuente.
Qué es realmente un paquete
Un paquete cumple tres propósitos a la vez:
- Un espacio de nombres.
java.util.Dateyjava.sql.Dateson clases diferentes; el nombre del paquete las mantiene separadas. - Un límite de acceso. Sin un modificador, los miembros solo son visibles dentro del mismo paquete — el nivel de acceso "paquete-privado". Es una forma real y estructural de encapsulación; consulta Modificadores de acceso.
- Un directorio. El nombre del paquete se asigna uno a uno a una ruta de carpeta.
com.example.app.utilvive encom/example/app/util/.
El mismo nombre se usa en tres lugares — declaración, ruta de archivo e import — y todos deben coincidir.
Convenciones de nombres
La convención de Java es el uso de nombres DNS inverso basado en un dominio que controlas:
- Todo en minúsculas:
com.example, noCom.Example. - Orden de dominio inverso: un proyecto en
w3docs.comusacom.w3docscomo raíz. - Le siguen segmentos de proyecto, módulo y funcionalidad:
com.w3docs.learnjava.parser. - Evita las palabras reservadas de Java como segmentos (
int,class,new). Si tu dominio incluye alguna, modifícala:com.example.int_o divide de otra forma.
Estas convenciones importan más allá de la estética. La regla del DNS inverso es lo que hace que los JARs de diferentes organizaciones puedan coexistir en el mismo classpath sin colisiones de nombres.
El paquete predeterminado
Un archivo .java sin declaración package pertenece al paquete predeterminado (sin nombre). Como consecuencia ocurren dos cosas:
- No puedes usar
importdesde el paquete predeterminado hacia uno con nombre. Todo lo que esté en él queda efectivamente aislado del código real. - Las herramientas de construcción, los IDEs y los sistemas de módulos tratan el paquete predeterminado como un caso degenerado — muchos directamente se niegan a compilar contra él.
Úsalo para archivos Hello.java de un solo uso. No publiques nada desde él.
Cómo los paquetes se asignan a directorios
Si declaras package com.w3docs.learnjava.parser; al inicio de Tokenizer.java, el archivo debe estar en:
com/w3docs/learnjava/parser/Tokenizer.javarelativo a la raíz de fuentes. El compilador no infiere el paquete a partir de la ruta — lee la declaración y confía en ti. Pero el entorno de ejecución (y la mayoría de las herramientas) no funcionará bien si ambos no coinciden.
Esa raíz de fuentes es donde comienza el laberinto del classpath: la JVM necesita saber dónde empieza el árbol de paquetes, o no puede encontrar nada.
Declarar un paquete
La sentencia package es lo que asigna una clase a un paquete. Debe ser la primera sentencia del archivo — antes de cualquier import, antes de la propia clase. Solo los comentarios y las líneas en blanco pueden precederla.
// File: com/w3docs/learnjava/parser/Tokenizer.java
package com.w3docs.learnjava.parser;
import java.util.List;
public class Tokenizer {
public List<String> tokenize(String source) {
// ...
return List.of();
}
}Desde cualquier otro lugar, esta clase ahora se conoce por su nombre completamente calificado, com.w3docs.learnjava.parser.Tokenizer. El código en el mismo paquete puede referirse a ella simplemente como Tokenizer; el código de otros paquetes debe importarla o deletrear el nombre completo.
package, y solo una clase public de nivel superior — cuyo nombre debe coincidir con el nombre del archivo. Si pones package en cualquier lugar que no sea el inicio, el compilador rechaza el archivo.Un ejemplo práctico: dos Loggers
El argumento más claro a favor de los paquetes es la colisión que evitan. El siguiente programa usa dos clases llamadas Logger en espíritu — el java.util.logging.Logger del JDK por su nombre completamente calificado — y muestra cómo el paquete de una clase se convierte en parte de su identidad en tiempo de ejecución.
Dos conclusiones al ejecutarlo: las clases conocen su propio paquete en tiempo de ejecución (a través de Class.getName() y Class.getPackage()), y el nombre completamente calificado es lo que identifica un tipo de forma inequívoca — Logger solo es ambiguo; java.util.logging.Logger no lo es.
Qué sigue
Nombrar un paquete es una cosa; traer sus tipos a tu propio código es otra. El siguiente capítulo cubre la sentencia import — importaciones de un solo tipo, importaciones con comodín, importaciones estáticas y cuándo cada una es la opción correcta.