Saltar al contenido
paquetes.java · devschool

Paquetes y acceso en Java

Lección 15 de 26 · 11 min de lectura · Actualizado el

En esta lección
  1. Qué es un paquete
  2. Declarar un paquete: package
  3. Importar clases: import
  4. Los cuatro niveles de acceso
  5. Compilar y ejecutar con paquetes desde la terminal
  6. Qué es un JAR y cómo crearlo
  7. Maven y Gradle
  8. Errores frecuentes
  9. Resumen

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:

  1. Organizar el código: las clases del modelo en un sitio, las utilidades en otro.
  2. Evitar choques de nombres: puede haber una clase Producto en tu tienda y otra en una librería que uses. Como están en paquetes distintos, no se confunden.
  3. 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, no Es.DevSchool.Tienda.
  • Se empieza por el dominio de la empresa al revés: si la web es devschool.es, los paquetes empiezan por es.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:

ModificadorMisma claseMismo paqueteSubclase en otro paqueteCualquier clase
publicSíSíSíSí
protectedSíSíSíNo
(ninguno) o package-privateSíSíNoNo
privateSíNoNoNo
  • 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 llamarse Producto.java.
  • class Ayudante: solo se puede usar dentro de su paquete. Un archivo puede tener varias clases así, pero solo una public.

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 llamado META-INF/MANIFEST.MF.
  • -C out .: mete todo el contenido de la carpeta out.

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/java y los tests en src/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.java dice package 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 Main falla con Could not find or load main class Main: hay que usar el nombre completo, es.devschool.tienda.app.Main.
  • Añadir .class o barras al ejecutar. java es/devschool/tienda/app/Main.class tampoco 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 protected para ocultar cosas. Cualquier clase del mismo paquete también puede acceder a lo protected.

Resumen

ConceptoQué 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 staticImporta métodos o constantes static
public / protected / default / privateDe más abierto a más cerrado
javac -d outCompila y deja los .class en out con la estructura de paquetes
java -cp out paquete.ClaseEjecuta indicando dónde están las clases
jar --createEmpaqueta las clases en un .jar
Maven / GradleAutomatizan 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

[Java] Un atributo declarado sin modificador de acceso (int stock;), ¿desde dónde se puede usar?

[Java] Has compilado con javac -d out y la clase con main es es.devschool.tienda.app.Main. ¿Qué comando la ejecuta?

[Java] ¿Qué ocurre al compilar este código?
import java.util.*;

public class Prueba {
    public static void main(String[] args) {
        Function<String, Integer> f = String::length;
    }
}

[Java] El archivo está en src/es/devschool/tienda/modelo/Producto.java. ¿Qué primera línea debe tener?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.