Saltar al contenido principal

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.