Encapsulamiento
En resumen: El encapsulamiento significa que una clase oculta sus datos con private y los muestra solo a través de sus propios métodos, getters y setters que comprueban los valores.
Problema
Menos 500 €
En la lección anterior pusimos la comprobación del retiro en un método. Pero el campo está abierto, y cualquiera puede escribir en él directamente:
Account account = new Account("María", 100);account.withdraw(500);account.balance = -500;System.out.println(account.balance);-500.0El método se negó con razón a retirar 500 €. La línea siguiente simplemente fijó el saldo a mano. La comprobación existe, pero se puede saltar. Necesitamos una forma de decir «solo la cuenta toca el saldo».
La idea
private: solo la clase lo toca
Pon private delante del campo en lugar de public:
class Account { private String owner; private double balance; // ...}Ahora solo el código de dentro de la clase Account puede llegar al campo. Desde fuera, la línea
account.balance = -500;ya no compila:
error: balance has private access in Accountprivate y public se llaman modificadores de acceso. Dicen quién puede ver un campo, un método o un constructor. La clase oculta sus datos y los muestra solo a través de sus propios métodos. Esto se llama encapsulamiento.
Más precisamente, para curiosos
- Sin modificador, un campo o un método es visible en el mismo paquete (un grupo de clases en una carpeta). Mientras todo está en un archivo, es casi como
public. Así que escribe el modificador de forma explícita. - También existe
protected: visible en la clase y en sus clases hijas. Lo conocerás en el módulo «Herencia y polimorfismo». public class Maines una clase pública. Un archivo puede tener solo una, y se llama igual que el archivo. Por eso nuestras clases de encima no llevanpublic.
La idea
Leer sin tocar
Desde fuera, ahora no podemos ni siquiera leer el saldo. Así que la clase ofrece un método que solo lo devuelve:
class Account { private String owner; private double balance;
public Account(String owner, double balance) { this.owner = owner; this.balance = balance; }
public String getOwner() { return owner; }
public double getBalance() { return balance; }
public void deposit(double amount) { if (amount > 0) { balance += amount; } }
public boolean withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; return true; } return false; }}
public class Main { public static void main(String[] args) { Account account = new Account("María", 100); account.withdraw(30); account.deposit(50); System.out.println(account.getOwner() + ": " + account.getBalance() + " €"); }}María: 120.0 €getBalance() es un getter: un método que devuelve el valor de un campo. Su nombre es get más el nombre del campo con mayúscula. El saldo cambia solo a través de deposit y withdraw, y ellos hacen las comprobaciones.
Ahora withdraw devuelve boolean: si el retiro salió bien. Así quien retira sabe si funcionó.
Account account = new Account("María", 100);account.withdraw(500);account.deposit(50);System.out.println(account.getBalance());Paso a paso, en palabras
- balance es private: el cristal significa que desde fuera no hay camino hacia el campo.
- El retiro pasa por la comprobación del método: 500 es más que 100. Rechazado, y el saldo se queda como está.
- El depósito también se comprueba: 50 es un importe positivo. El método lo suma.
- getBalance() entrega una copia del saldo. El campo en sí sigue oculto.
Pruébalo
Rechazado
¿Qué imprimirá el programa?
Account account = new Account("María", 100);System.out.println(account.withdraw(150));account.deposit(-20);System.out.println(account.getBalance());150 € es más que el saldo, así que withdraw devuelve false y no toca el saldo. El depósito de -20 € también se rechaza, porque el importe tiene que ser positivo. El saldo se queda en 100.
La idea
Setter: un cambio con comprobación
A veces el código de fuera necesita cambiar un campo, pero no a cualquier valor. La edad de un animal puede cambiar, pero no puede ser negativa:
class Animal { private int age;
public int getAge() { return age; }
public void setAge(int age) { if (age >= 0) { this.age = age; } }}
Animal cat = new Animal();cat.setAge(4);cat.setAge(-3);System.out.println(cat.getAge());4setAge es un setter: un método que guarda un valor en un campo después de comprobarlo. Con -3, la comprobación falla y la edad se queda en 4.
La idea
¿Qué campo lleva setter?
No todos los campos necesitan un setter, y algunos ni siquiera un getter. Piensa en lo que el código de fuera debería poder hacer con el producto:
- el nombre y el precio se fijan al crear el producto y después solo se leen: solo getter;
- la cantidad en el carrito cambia, pero es como mínimo 1: un getter y un setter con comprobación.
class Product { private String name; private double price; private int quantity;
public Product(String name, double price) { this.name = name; this.price = price; this.quantity = 1; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { if (quantity >= 1) { this.quantity = quantity; } }}
Product coffee = new Product("Café", 7.50);coffee.setQuantity(3);coffee.setQuantity(0);System.out.println(coffee.getName() + " x" + coffee.getQuantity());Café x3La regla es: empieza con private para todo y abre solo lo que necesitas. Un setter que guarda cualquier cosa sin comprobar trae de vuelta el problema de la primera tarjeta.
En Java, los getters y los setters se escriben a mano. IntelliJ IDEA puede generarlos: Alt+Insert → Getter and Setter.
Pruébalo
¿Qué falta?
class Account {
double balance;
}private: el campo solo lo ve el código de Account.Pruébalo
Encuentra el error
El campo es private, y aun así el saldo pasa a ser -500:
Account account = new Account();account.setBalance(100);account.withdraw(30);account.setBalance(-500);System.out.println(account.getBalance());-500.0¿Qué línea de la clase rompe la protección?
Elige la línea con el error.
setBalance guarda cualquier valor sin comprobación. El campo está oculto, pero el setter lo vuelve a abrir. Quítalo y fija el saldo inicial en el constructor. Después solo cambia a través de deposit y withdraw.
Ejercicio
Datos ocultos
Resuelve las tareas en tu editor. Todos los campos son private.
1. Persona. Person con un nombre y una edad, un constructor, los getters getName() y getAge() y introduce().
María tiene 34 añosHola, soy María y tengo 34 años2. Una cuenta sin números rojos. Account con un titular y un saldo. Un depósito acepta solo un importe positivo. Un retiro devuelve si salió bien y nunca deja que el saldo baje de 0. El código de fuera puede leer el saldo, pero no escribirlo.
Account account = new Account("Pablo", 50);account.deposit(25);account.deposit(-10);System.out.println(account.withdraw(100));System.out.println(account.withdraw(40));System.out.println("Saldo: " + account.getBalance() + " €");falsetrueSaldo: 35.0 €3. Animal. Animal con una especie y una edad. La especie se fija en el constructor y después solo se lee. La edad tiene un setter que rechaza un número negativo e imprime un mensaje. El constructor también pasa por la misma comprobación.
Animal cat = new Animal("gato", 3);cat.setAge(4);cat.setAge(-1);System.out.println(cat.getSpecies() + ", " + cat.getAge() + " años");Edad no válida: -1gato, 4 añosMás tareas
- Nota. Una clase
Gradecon el nombre de una asignatura y una nota de 2 a 6. El setter rechaza todo lo que está fuera de esos límites. Pruébalo con 5, 7 y 1. - Producto con descuento. Añade al
Productde la lección un método que aplica un descuento en porcentaje y acepta solo del 1 al 90 %. El precio no tiene setter.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Parámetros y return · Clases y objetos · Constructores y this · Métodos de instancia
- Continúa en: static: lo que comparten todos los objetos · Herencia · Polimorfismo · Clases abstractas
- Ejercicios sobre el tema: Clases y objetos