Paquetes y acceso en Java
En esta lección
Mientras tu programa tiene tres o cuatro clases, puedes dejarlas todas en la misma carpeta. Pero una aplicación real tiene decenas o cientos, y necesita orden. Java las organiza en paquetes, que funcionan como carpetas con nombre. En esta lección aprenderás a crear paquetes, a importar clases de otros, a controlar quién puede ver cada cosa con los cuatro niveles de acceso y a compilar, ejecutar y empaquetar un proyecto desde la terminal.
El ejemplo que construiremos es una pequeña tienda online con productos, un inventario y utilidades para mostrar precios.
Qué es un paquete
Un paquete agrupa clases relacionadas bajo un nombre común. Sirve para tres cosas:
- Organizar el código: las clases del modelo en un sitio, las utilidades en otro.
- Evitar choques de nombres: puede haber una clase
Productoen tu tienda y otra en una librería que uses. Como están en paquetes distintos, no se confunden. - Controlar el acceso: hay cosas que solo pueden ver las clases del mismo paquete.
Llevas usando paquetes desde el principio sin saberlo. String está en java.lang, Scanner en java.util y LocalDate en java.time.
Declarar un paquete: package
La primera línea de un archivo .java (antes de los import) indica a qué paquete pertenece:
package es.devschool.tienda.modelo;
public class Producto {
private String nombre;
private double precio;
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
public String getNombre() { return nombre; }
}
Paquetes y carpetas
El nombre del paquete tiene que coincidir con la estructura de carpetas. Cada punto es un nivel. Nuestra tienda queda así:
tienda/
└── src/
└── es/
└── devschool/
└── tienda/
├── app/
│ └── Main.java
├── modelo/
│ ├── Inventario.java
│ └── Producto.java
└── util/
└── Formato.java
Producto.java está en es/devschool/tienda/modelo/, así que su paquete es es.devschool.tienda.modelo. Los entornos como IntelliJ IDEA, Eclipse o VS Code crean las carpetas por ti cuando creas un paquete.
Convenciones de nombres
- Todo en minúsculas, sin tildes ni guiones:
es.devschool.tienda, noEs.DevSchool.Tienda. - Se empieza por el dominio de la empresa al revés: si la web es
devschool.es, los paquetes empiezan pores.devschool. Como los dominios son únicos, los nombres de paquete también lo serán. - Después va el proyecto y la parte del proyecto:
es.devschool.tienda.modelo,es.devschool.tienda.util.
Consejo: si no pones
package, la clase queda en el paquete por defecto (sin nombre). Vale para ejercicios pequeños, pero en un proyecto real todas las clases deben estar en un paquete.
Importar clases: import
Para usar una clase de otro paquete tienes dos opciones. La primera es escribir su nombre completo:
es.devschool.tienda.modelo.Producto teclado =
new es.devschool.tienda.modelo.Producto("Teclado", 40.0);
Funciona, pero es incómodo. Lo normal es importarla una vez al principio del archivo y usar solo su nombre corto:
package es.devschool.tienda.app;
import es.devschool.tienda.modelo.Producto;
public class Main {
public static void main(String[] args) {
Producto teclado = new Producto("Teclado", 40.0);
System.out.println(teclado.getNombre()); // Teclado
}
}
Importar un paquete entero con *
import java.util.*; // ArrayList, List, Scanner, Random...
El asterisco importa todas las clases de ese paquete, pero no las de sus subpaquetes: import java.util.*; no importa java.util.function.Function. Muchos equipos prefieren importar clase a clase para ver de un vistazo qué se usa; los entornos de desarrollo lo hacen automáticamente.
Importaciones estáticas: import static
Con import static traes miembros static (métodos o constantes) para usarlos sin escribir el nombre de la clase:
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
double r = sqrt(16) * PI; // en lugar de Math.sqrt(16) * Math.PI
Úsalo con moderación: si importas muchos métodos estáticos, no se sabe de dónde viene cada uno.
Lo que no hay que importar
- Las clases de
java.lang(String,Math,Integer,System…), que se importan solas. - Las clases del mismo paquete que la tuya.
Los cuatro niveles de acceso
En encapsulación usaste public y private. En realidad Java tiene cuatro niveles. De más abierto a más cerrado:
| Modificador | Misma clase | Mismo paquete | Subclase en otro paquete | Cualquier clase |
|---|---|---|---|---|
public | Sí | Sí | Sí | Sí |
protected | Sí | Sí | Sí | No |
| (ninguno) o package-private | Sí | Sí | No | No |
private | Sí | No | No | No |
public: lo ve todo el mundo. Para la parte de la clase que quieres ofrecer a los demás.protected: el paquete y las subclases (aunque estén en otro paquete). Lo verás en herencia.- Sin modificador (default o package-private): solo las clases del mismo paquete. Útil para detalles internos que comparten varias clases de un mismo módulo.
private: solo la propia clase.
Veamos los cuatro en nuestra tienda:
package es.devschool.tienda.modelo;
public class Producto {
private String nombre; // solo dentro de Producto
double precio; // package-private: todo el paquete modelo
protected int stock; // paquete modelo + subclases
public static final double IVA = 0.21;
public Producto(String nombre, double precio, int stock) {
this.nombre = nombre;
this.precio = precio;
this.stock = stock;
}
public String getNombre() { return nombre; }
public double precioConIva() {
return precio * (1 + IVA);
}
}
Inventario está en el mismo paquete, así que puede leer precio y stock directamente:
package es.devschool.tienda.modelo;
public class Inventario {
public static double valorTotal(Producto p) {
return p.precio * p.stock; // mismo paquete: puede ver precio y stock
}
}
Una subclase en otro paquete ve stock (es protected) pero no precio:
package es.devschool.tienda.ofertas;
import es.devschool.tienda.modelo.Producto;
public class ProductoRebajado extends Producto {
public ProductoRebajado(String nombre, double precio, int stock) {
super(nombre, precio, stock);
}
public boolean quedanPocos() {
return stock < 3; // protected: la subclase sí lo ve
}
public double precioBase() {
return precio;
// error: precio is not public in Producto; cannot be accessed from outside package
}
}
Y una clase cualquiera de otro paquete solo ve lo public:
Producto p = new Producto("Ratón", 15.0, 10);
p.getNombre(); // bien: es public
p.nombre; // error: nombre has private access in Producto
p.stock; // error: stock has protected access in Producto
Acceso en clases
Una clase de primer nivel solo puede ser public o no llevar modificador:
public class Producto: se puede usar desde cualquier paquete. El archivo debe llamarseProducto.java.class Ayudante: solo se puede usar dentro de su paquete. Un archivo puede tener varias clases así, pero solo unapublic.
Consejo: empieza con todo lo más cerrado posible (
private) y abre solo lo necesario. Es mucho más fácil abrir algo después que cerrarlo cuando ya hay código que depende de ello.
Compilar y ejecutar con paquetes desde la terminal
Los entornos de desarrollo lo hacen todo con un botón, pero conviene saber qué pasa por debajo. Completamos la tienda con una clase de utilidad y el programa principal:
package es.devschool.tienda.util;
public class Formato {
public static String euros(double cantidad) {
return String.format("%.2f €", cantidad);
}
}
package es.devschool.tienda.app;
import es.devschool.tienda.modelo.Inventario;
import es.devschool.tienda.modelo.Producto;
import static es.devschool.tienda.util.Formato.euros;
public class Main {
public static void main(String[] args) {
Producto teclado = new Producto("Teclado", 40.0, 5);
System.out.println(teclado.getNombre()); // Teclado
System.out.println(euros(teclado.precioConIva())); // 48,40 €
System.out.println(euros(Inventario.valorTotal(teclado))); // 200,00 €
}
}
String.format usa el idioma del sistema: en un ordenador configurado en español muestra 48,40 €, y en uno en inglés, 48.40 €.
Compilar con javac -d
Desde la carpeta tienda/:
javac -d out src/es/devschool/tienda/modelo/*.java src/es/devschool/tienda/util/*.java src/es/devschool/tienda/app/Main.java
La opción -d out indica dónde dejar los .class. javac crea dentro de out la misma estructura de carpetas que los paquetes:
out/
└── es/devschool/tienda/
├── app/Main.class
├── modelo/Inventario.class
├── modelo/Producto.class
└── util/Formato.class
Así el código fuente y el compilado quedan separados. En Linux y macOS puedes compilar todos los archivos de golpe con javac -d out $(find src -name "*.java").
Ejecutar con java -cp
java -cp out es.devschool.tienda.app.Main
-cp out(de classpath) le dice a Java en qué carpeta buscar las clases.- Después va el nombre completo de la clase con
main, con puntos y sin.class.
El classpath
El classpath es la lista de lugares (carpetas y archivos JAR) donde la máquina virtual busca las clases. Si tu programa usa una librería externa, tienes que añadirla. Los elementos se separan con : en Linux y macOS y con ; en Windows:
java -cp out:lib/gson.jar es.devschool.tienda.app.Main # Linux / macOS
java -cp "out;lib\gson.jar" es.devschool.tienda.app.Main # Windows
Qué es un JAR y cómo crearlo
Un JAR (Java ARchive) es un archivo .jar que contiene todas las clases compiladas de un programa o librería, comprimidas en formato ZIP. Es la forma habitual de distribuir código Java: las librerías que descargues serán JAR, y tu aplicación terminada también.
Para crear un JAR ejecutable de la tienda:
jar --create --file tienda.jar --main-class es.devschool.tienda.app.Main -C out .
--file tienda.jar: el archivo que se crea.--main-class: la clase que se ejecutará al lanzar el JAR. Queda anotada en un archivo interno llamadoMETA-INF/MANIFEST.MF.-C out .: mete todo el contenido de la carpetaout.
Y para ejecutarlo:
java -jar tienda.jar
Puedes ver lo que hay dentro con jar --list --file tienda.jar.
Maven y Gradle
Compilar a mano está bien para entender el proceso, pero en proyectos reales se usa una herramienta de construcción. Las dos más usadas son Maven y Gradle. A partir de un archivo de configuración (pom.xml o build.gradle) se encargan de:
- Descargar las librerías que necesita el proyecto (las dependencias).
- Compilar, ejecutar los tests y generar el JAR con un solo comando.
- Imponer una estructura de carpetas estándar: el código va en
src/main/javay los tests ensrc/test/java.
Cuando crees un proyecto nuevo en IntelliJ IDEA, te preguntará si quieres usar Maven o Gradle. Cualquiera de los dos vale para empezar.
Errores frecuentes
- El paquete no coincide con la carpeta. Si
Producto.javadicepackage es.devschool.tienda.modelo;pero está en otra carpeta, tu entorno marcará error y al ejecutar Java no encontrará la clase. - Ejecutar con el nombre corto.
java -cp out Mainfalla con Could not find or load main class Main: hay que usar el nombre completo,es.devschool.tienda.app.Main. - Añadir
.classo barras al ejecutar.java es/devschool/tienda/app/Main.classtampoco funciona. Se escribe con puntos y sin extensión. - Olvidar el
-cp. Si ejecutas desde una carpeta que no es la raíz de los.class, Java no los encuentra. - Pensar que sin modificador es
public. En Java, no poner nada significa package-private: desde otro paquete no se ve. - Confiar en
protectedpara ocultar cosas. Cualquier clase del mismo paquete también puede acceder a loprotected.
Resumen
| Concepto | Qué hace |
|---|---|
package a.b.c; | Declara el paquete; debe coincidir con la carpeta a/b/c/ |
import a.b.Clase; | Permite usar la clase por su nombre corto |
import a.b.*; | Importa todas las clases del paquete (no los subpaquetes) |
import static | Importa métodos o constantes static |
public / protected / default / private | De más abierto a más cerrado |
javac -d out | Compila y deja los .class en out con la estructura de paquetes |
java -cp out paquete.Clase | Ejecuta indicando dónde están las clases |
jar --create | Empaqueta las clases en un .jar |
| Maven / Gradle | Automatizan dependencias, compilación y empaquetado |
Con las clases ya organizadas, el siguiente paso es reutilizar código entre ellas con la herencia.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.