Atributos, métodos y constructores
En JV2-01 creamos objetos y asignamos sus datos después. Ese proceso permite olvidar un dato importante o dejar temporalmente un objeto sin sentido. Una clase útil debe expresar qué estado necesita y qué operaciones sabe realizar.
Estado y comportamiento
Los atributos guardan el estado particular de cada objeto. Los métodos de instancia describen operaciones que se ejecutan sobre ese objeto:
class Cuenta {
String titular;
double saldo;
void ingresar(double cantidad) {
saldo += cantidad;
}
void mostrarResumen() {
System.out.println(titular + ": " + saldo);
}
}
Cuenta cuenta = new Cuenta();
cuenta.titular = "Ana";
cuenta.ingresar(50);
cuenta.mostrarResumen();
La expresión situada antes del punto es el objeto receptor. cuenta.ingresar(50) modifica el saldo de cuenta, no el de todas las cuentas.
Métodos de instancia frente a static
En Java 1 usamos métodos static para llamarlos desde main. Un método de instancia necesita un objeto receptor y puede trabajar directamente con su estado:
cuenta.ingresar(50); // método de instancia: usa cuenta.saldo
Math.max(4, 7); // método static: se llama mediante la clase
No elegimos static solo para evitar crear objetos. Lo usamos cuando la operación pertenece al tipo o no necesita el estado de una instancia.
Construir un objeto válido
Un constructor se ejecuta al crear el objeto y establece su estado inicial:
class Cuenta {
String titular;
double saldo;
Cuenta(String titular, double saldoInicial) {
this.titular = titular;
saldo = saldoInicial;
}
}
Cuenta ahorro = new Cuenta("Ana", 100);
Un constructor:
- tiene el mismo nombre que la clase;
- no declara tipo de retorno, ni siquiera
void; - recibe argumentos como un método;
- prepara la nueva instancia.
this.titular nombra el atributo del objeto actual. Profundizaremos en this y visibilidad en JV2-03.
Práctica inmediata
¿Qué ocurre si escribimos void Cuenta(...)? Ya no es un constructor: sería un método llamado Cuenta, y new Cuenta("Ana", 100) no encontraría el constructor esperado.
Objetos con estado independiente
Cuenta ana = new Cuenta("Ana", 100);
Cuenta luis = new Cuenta("Luis", 30);
ana.ingresar(20);
System.out.println(ana.saldo); // 120.0
System.out.println(luis.saldo); // 30.0
El método se declara una vez, pero cada llamada utiliza el estado del receptor.
Sobrecarga de constructores
Podemos ofrecer formas distintas de crear una cuenta:
class Cuenta {
String titular;
double saldo;
Cuenta(String titular) {
this(titular, 0);
}
Cuenta(String titular, double saldoInicial) {
this.titular = titular;
saldo = saldoInicial;
}
}
this(titular, 0) llama al otro constructor y evita duplicar la inicialización. Igual que en la sobrecarga de métodos, las listas de parámetros deben ser distintas.
Responsabilidades
Una clase no debe limitarse a ser una bolsa de datos. Si una operación depende del estado de una cuenta, tiene sentido preguntarse si la propia Cuenta debe realizarla.
cuenta.ingresar(25);
expresa mejor la intención que:
cuenta.saldo = cuenta.saldo + 25;
En la próxima unidad protegeremos el atributo para que las reglas no puedan saltarse desde fuera.
Programa completo
Guárdalo como CuentasDemo.java:
class Cuenta {
String titular;
double saldo;
Cuenta(String titular) {
this(titular, 0);
}
Cuenta(String titular, double saldoInicial) {
this.titular = titular;
saldo = saldoInicial;
}
void ingresar(double cantidad) {
saldo += cantidad;
}
void mostrarResumen() {
System.out.println(titular + ": " + saldo);
}
}
public class CuentasDemo {
public static void main(String[] args) {
Cuenta ana = new Cuenta("Ana", 100);
Cuenta luis = new Cuenta("Luis");
ana.ingresar(20);
luis.ingresar(35);
ana.mostrarResumen();
luis.mostrarResumen();
}
}
Salida:
Ana: 120.0
Luis: 35.0
Errores habituales
- Declarar un tipo de retorno en un constructor.
- Confundir los parámetros del constructor con atributos compartidos por todos los objetos.
- Llamar a un método de instancia como si fuera
static. - Crear un objeto sin establecer el estado que necesita.
- Añadir operaciones ajenas a la responsabilidad de la clase.
Mini reto: libro disponible
Crea una clase Libro con título y número de ejemplares disponibles. Ofrece un constructor con título y ejemplares, otro que reciba solo el título y empiece con un ejemplar, y un método para añadir ejemplares.
Solución razonada
class Libro {
String titulo;
int disponibles;
Libro(String titulo) {
this(titulo, 1);
}
Libro(String titulo, int disponibles) {
this.titulo = titulo;
this.disponibles = disponibles;
}
void reponer(int cantidad) {
disponibles += cantidad;
}
void mostrar() {
System.out.println(titulo + ": " + disponibles);
}
}
public class LibrosDemo {
public static void main(String[] args) {
Libro java = new Libro("Java práctico");
Libro algoritmos = new Libro("Algoritmos", 3);
java.reponer(2);
java.mostrar();
algoritmos.mostrar();
}
}
Los constructores garantizan un punto de partida y reponer concentra la operación que cambia el estado. Todavía no impedimos valores inválidos; ese será el objetivo de la encapsulación.