Encapsulación, visibilidad y this
Una clase pierde el control de sus reglas si cualquier parte del programa puede escribir directamente sus atributos:
cuenta.saldo = -1000;
Si una cuenta no admite saldo negativo, ese estado nunca debería llegar a existir. Encapsular consiste en proteger el estado y ofrecer operaciones públicas que mantengan sus invariantes.
Estado privado y API pública
class Cuenta {
private String titular;
private double saldo;
public Cuenta(String titular, double saldoInicial) {
this.titular = titular;
this.saldo = saldoInicial;
}
}
privatelimita el acceso a la propia clase.publicpermite usar el constructor u operación desde otras clases.- sin modificador, el miembro es package-private: resulta accesible desde clases del mismo package. Lo retomaremos al organizar packages.
La visibilidad no se elige por costumbre: expresa quién debe poder usar o cambiar cada pieza.
this: el objeto actual
Cuando un parámetro y un atributo comparten nombre, this distingue el atributo del objeto receptor:
public Cuenta(String titular) {
this.titular = titular;
}
Sin this, la asignación titular = titular asignaría el parámetro a sí mismo y no inicializaría el atributo.
Consultar sin exponer
Un getter permite consultar un dato privado cuando el exterior realmente lo necesita:
public double getSaldo() {
return saldo;
}
Eso no obliga a crear un setter. Este diseño sería peligroso:
public void setSaldo(double saldo) {
this.saldo = saldo;
}
Permitiría setSaldo(-1000) y no expresa la operación del dominio. Para una cuenta son más coherentes ingresar y retirar.
Comportamiento que protege reglas
public boolean retirar(double cantidad) {
if (cantidad <= 0 || cantidad > saldo) {
return false;
}
saldo -= cantidad;
return true;
}
La regla queda en un único lugar. El estado válido que siempre queremos conservar se denomina invariante.
Práctica inmediata
Para cambiar el nombre de un alumno, ¿crearías siempre setNombre? Depende. Si el nombre debe estar limpio y no vacío, una operación cambiarNombre puede validar y comunicar mejor la intención. Si nunca debe cambiar, no ofrezcas ninguna operación.
final no significa objeto inmutable
Un atributo final solo puede asignarse una vez:
class Pedido {
private final String codigo;
Pedido(String codigo) {
this.codigo = codigo;
}
}
Con referencias, final impide apuntar a otro objeto, pero no congela el objeto referenciado:
final Cuenta cuenta = new Cuenta("Ana", 100);
cuenta.ingresar(20); // válido: cambia el objeto
// cuenta = new Cuenta(...); // no compila: reasigna la referencia
La inmutabilidad requiere un diseño más amplio: estado privado y ausencia de operaciones que lo modifiquen, entre otras decisiones.
Programa completo
Guarda como CuentaSeguraDemo.java:
class Cuenta {
private final String titular;
private double saldo;
public Cuenta(String titular, double saldoInicial) {
this.titular = titular;
this.saldo = saldoInicial >= 0 ? saldoInicial : 0;
}
public String getTitular() {
return titular;
}
public double getSaldo() {
return saldo;
}
public boolean ingresar(double cantidad) {
if (cantidad <= 0) {
return false;
}
saldo += cantidad;
return true;
}
public boolean retirar(double cantidad) {
if (cantidad <= 0 || cantidad > saldo) {
return false;
}
saldo -= cantidad;
return true;
}
}
public class CuentaSeguraDemo {
public static void main(String[] args) {
Cuenta cuenta = new Cuenta("Ana", 100);
System.out.println("Retirada 30: " + cuenta.retirar(30));
System.out.println("Retirada 100: " + cuenta.retirar(100));
System.out.println("Ingreso -5: " + cuenta.ingresar(-5));
System.out.println(cuenta.getTitular() + ": " + cuenta.getSaldo());
}
}
El segundo retiro y el ingreso negativo se rechazan, de modo que el saldo nunca queda en un estado inválido.
Decisiones alternativas
- Getter sin setter: lectura pública, cambio controlado.
- Operación de comportamiento: expresa intención y protege reglas.
- Constructor validado: evita empezar con un estado imposible.
package-private: útil para colaboración interna de un package, no para evitar pensar la API.
Errores habituales
- Crear getter y setter para cada atributo de forma automática.
- Validar solo en
main, dejando que otra clase pueda romper la regla. - Confundir
this.saldocon una variable global. - Creer que
finalvuelve inmutable cualquier objeto. - Hacer todo
publicpara resolver errores de acceso.
Mini reto: producto con stock válido
Diseña Producto con nombre no reasignable y stock privado. El stock no puede ser negativo. Ofrece reponer, vender y getters necesarios; no crees setStock.
Solución razonada
class Producto {
private final String nombre;
private int stock;
public Producto(String nombre, int stockInicial) {
this.nombre = nombre;
this.stock = stockInicial >= 0 ? stockInicial : 0;
}
public String getNombre() {
return nombre;
}
public int getStock() {
return stock;
}
public boolean reponer(int cantidad) {
if (cantidad <= 0) return false;
stock += cantidad;
return true;
}
public boolean vender(int cantidad) {
if (cantidad <= 0 || cantidad > stock) return false;
stock -= cantidad;
return true;
}
}
public class ProductoDemo {
public static void main(String[] args) {
Producto teclado = new Producto("Teclado", 3);
System.out.println(teclado.vender(2));
System.out.println(teclado.vender(2));
System.out.println(teclado.getNombre() + ": " + teclado.getStock());
}
}
La API permite operaciones reales y conserva stock >= 0. En JV2-04 haremos que varios objetos encapsulados colaboren sin concentrar todas las responsabilidades en una clase.