El encapsulamiento consiste en limitar el acceso al estado interno de un objeto y exponer operaciones controladas.
En el artículo anterior dejamos nuestros objetos Robot con sus atributos expuestos. Cualquier parte del programa podía hacer esto:
miRobot.nivelBateria = -5000; // ¡Destrozando la lógica!
miRobot.nombre = null; // Rompiendo el sistemaSi permitimos el acceso directo a los datos, perdemos el control. No podemos validar si los datos son correctos.
Este problema nos lleva al segundo pilar de la programación orientada a objetos: el encapsulamiento.
El encapsulamiento consiste en ocultar los detalles internos de una clase y exponer solo lo que sea seguro y necesario.
Los modificadores de acceso
Java nos da cuatro niveles de seguridad para decidir “quién puede ver qué”. Se aplican a clases, atributos y métodos.
| Modificador | Misma Clase | Mismo Paquete | Subclase (otro paquete) | Mundo (Todo) |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ |
| Default | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
Es el nivel más permisivo. Cualquiera, desde cualquier parte del proyecto, puede acceder.
- Uso: Métodos que forman parte de la API pública (lo que quieres que usen los demás).
Es el nivel más restrictivo. Solo la propia clase puede ver esos datos. Ni siquiera las clases vecinas.
- Uso: TODOS los atributos deberían ser privados por defecto.
Es un nivel intermedio. Permite el acceso a:
- La propia clase.
- Las clases del mismo paquete.
- Las subclases (hijas), aunque estén en otro paquete.
- Uso: Lo veremos a fondo cuando lleguemos a la Herencia.
Si no escribes nada (ni public, ni private…), Java aplica este nivel por defecto. Permite el acceso a cualquier clase dentro del mismo paquete.
- Uso: Clases de utilidad internas que solo sirven para ayudar a otras clases del módulo.
Getters y setters: los porteros de discoteca
Si ponemos todos los atributos private (como debe ser), nadie podrá leer ni cambiar el nombre de nuestro Robot. El objeto sería una caja negra inútil.
Para permitir el acceso controlado, creamos métodos públicos especiales: los Getters (para leer) y los Setters (para escribir).
¿Por qué hacer esto y no dejarlo público?
La ventaja de un setter es que permite ejecutar validaciones antes de guardar el dato.
public class Robot {
// 1. ATRIBUTO PRIVADO (Blindado)
private int nivelBateria;
// 2. GETTER (Lectura)
public int getNivelBateria() {
return this.nivelBateria;
}
// 3. SETTER (Escritura controlada)
public void setNivelBateria(int nuevoNivel) {
// VALIDACIÓN: Evitamos valores absurdos
if (nuevoNivel < 0) {
System.out.println("Error: La batería no puede ser negativa. Se pone a 0.");
this.nivelBateria = 0;
} else if (nuevoNivel > 100) {
this.nivelBateria = 100;
} else {
this.nivelBateria = nuevoNivel;
}
}
}Ahora, desde fuera:
Robot r = new Robot();
// r.nivelBateria = -50; // ERROR: No compila, es privado.
r.setNivelBateria(-50); // El setter intercepta el error y lo corrige a 0.
System.out.println(r.getNivelBateria()); // Leemos de forma segura.Objetos de solo lectura (inmutabilidad)
Si quieres que un atributo sea de lectura pero no se pueda modificar una vez creado, simplemente no crees el Setter.
public class DNI {
private String numero;
public DNI(String n) {
this.numero = n;
}
// Solo Getter. Nadie podrá cambiar el número del DNI después de nacer.
public String getNumero() {
return numero;
}
}