Control de visibilidad
public, private, protected, package-private y paquetes
Los cuatro niveles de acceso
Java tiene cuatro niveles de acceso. El predeterminado (sin modificador) es package-private — visible solo dentro del mismo paquete. Es el nivel más olvidado y a menudo el más útil para clases de implementación internas.
public class EjemploVisibilidad {
public String publico; // accesible desde cualquier lugar
protected String protegido; // accesible desde el mismo paquete y subclases
String packagePriv; // package-private: solo el mismo paquete (sin modificador)
private String privado; // solo dentro de esta clase
} // Resumen de acceso por contexto:
//
// Modificador | Misma clase | Mismo paquete | Subclase | Resto
// ---------------|-------------|---------------|----------|-------
// public | ✓ | ✓ | ✓ | ✓
// protected | ✓ | ✓ | ✓ | ✗
// (sin modif.) | ✓ | ✓ | ✗ | ✗
// private | ✓ | ✗ | ✗ | ✗ El sistema de paquetes
Los paquetes organizan el código y son la unidad de visibilidad package-private. Un paquete es un namespace: com.empresa.servicio y com.empresa.repositorio son paquetes distintos aunque compartan prefijo. La estructura de directorios
debe coincidir exactamente con la declaración package.
// Los paquetes son la unidad de visibilidad en Java
// Estructura de directorios = estructura de paquetes
// src/com/empresa/servicio/UsuarioService.java
package com.empresa.servicio;
import com.empresa.modelo.Usuario; // import explícito
import com.empresa.repositorio.*; // import comodín (evitar en producción)
public class UsuarioService {
// ...
} protected: el nivel más malinterpretado
protected permite acceso a subclases en cualquier paquete, además de todo el paquete actual.
Se usa habitualmente en clases base de frameworks (plantillas, repositorios) para exponer operaciones a subclases
sin hacerlas públicas.
// protected: accesible en subclases aunque estén en otro paquete
package com.empresa.base;
public class Repositorio {
protected Connection obtenerConexion() { // accesible por subclases
return DataSource.getConnection();
}
}
// --- en otro paquete ---
package com.empresa.impl;
import com.empresa.base.Repositorio;
public class UsuarioRepositorio extends Repositorio {
public Usuario buscar(Long id) {
Connection con = obtenerConexion(); // OK: es subclase
// ...
}
} Regla de mínima exposición
El principio general es usar siempre el modificador más restrictivo que permita al código funcionar. Cuanto más se expone, más difícil es cambiar la implementación sin romper el código que depende de ella.
// Regla general: usar el modificador más restrictivo posible
public class Pedido {
private Long id; // nunca exponer estado mutable directamente
private List<LineaPedido> lineas;
private EstadoPedido estado;
// Constructor: único punto de creación válida
public Pedido(Long id) {
this.id = id;
this.lineas = new ArrayList<>();
this.estado = EstadoPedido.BORRADOR;
}
// Getter sin setter: id es inmutable una vez creado
public Long getId() { return id; }
// Comportamiento como método, no como setter directo
public void confirmar() {
if (lineas.isEmpty()) throw new IllegalStateException("Pedido vacío");
estado = EstadoPedido.CONFIRMADO;
}
// Vista defensiva: no exponer la lista interna directamente
public List<LineaPedido> getLineas() {
return Collections.unmodifiableList(lineas);
}
}