Saltar a contenido

Semana 4 - Constructores, encapsulación y métodos de acceso en Java


1. Introducción

Una clase Java que almacena información sensible (saldo bancario, notas de un alumno, etc.) debe impedir que valores inválidos lleguen a sus atributos.
Para ello se combinan:

  • Constructores: crean objetos en un estado válido desde el nacimiento.
  • Encapsulamiento: ocultar los atributos (private) y exponer solo interfaces controladas.
  • Métodos de acceso (get/set): puertas de entrada y salida que validan y transforman la información.

2. Constructores en profundidad

Un constructor no devuelve nada, ni siquiera void, y lleva el mismo nombre que la clase.

2.1 Constructor implícito

Si no escribes ninguno, Java añade el constructor por defecto sin parámetros:

public class Producto { }   // Constructor implícito: new Producto()

2.2 Constructor sin argumentos (explicit)

Cuando necesitas inicializar valores por defecto:

public class Producto {
    private String nombre;
    private double precio;

    public Producto() {
        this.nombre = "Sin nombre";
        this.precio  = 0.0;
    }
}

2.3 Constructor con parámetros

Permite inicializar con valores concretos y validar inmediatamente:

public Producto(String nombre, double precio) {
    setNombre(nombre);   // reutilizo la validación del setter
    setPrecio(precio);
}

2.5 Constructores por defecto con Builder (opcional avanzado)

Para muchos parámetros opcionales se usa el patrón Builder:

public class Producto {
    private final String nombre;
    private final double precio;
    private final int stock;

    private Producto(Builder b) {
        this.nombre = b.nombre;
        this.precio = b.precio;
        this.stock  = b.stock;
    }

    public static class Builder {
        private String nombre;
        private double precio;
        private int stock;

        public Builder nombre(String val) { nombre = val; return this; }
        public Builder precio(double val) { precio = val; return this; }
        public Builder stock(int val)   { stock = val; return this; }
        public Producto build()         { return new Producto(this); }
    }
}

// Uso
Producto p = new Producto.Builder()
              .nombre("Mouse")
              .precio(29.99)
              .stock(100)
              .build();

3. Encapsulamiento de atributos

Regla de oro: todos los atributos de estado deben ser private.
De esta forma solo la propia clase puede modificarlos directamente.

public class CuentaBancaria {
    private String iban;
    private double saldo;
    private String titular;
}

4. Métodos de acceso (getters y setters)

4.1 Getter

Devuelve una copia o vista inmutable del dato.

public String getIban() {
    return iban;
}

public double getSaldo() {
    return saldo;   // primitivo → copia automática
}

⚠️ Si devolvieras objetos mutables (ej. Date, List) devuelve una copia defensiva.

4.2 Setter

Valida y asigna. Es la única vía oficial para cambiar el valor.

public void setSaldo(double saldo) {
    if (saldo < 0) {
        throw new IllegalArgumentException("El saldo no puede ser negativo");
    }
    this.saldo = saldo;
}

4.3 Inmutabilad parcial (solo getter)

Cuando un atributo no debe cambiar después de la construcción, no incluyas setter:

public class Dni {
    private final String numero;

    public Dni(String numero) {
        if (!numero.matches("\\d{8}[A-Z]")) {
            throw new IllegalArgumentException("Formato incorrecto");
        }
        this.numero = numero;
    }

    public String getNumero() {   // No existe setNumero
        return numero;
    }
}

5. Ejemplo completo: clase Alumno

package modelo;

public class Alumno {
    // 1. Atributos encapsulados
    private String dni;
    private String nombre;
    private double notaMedia;

    // 2. Constructores
    public Alumno() {
        this("00000000X", "Sin nombre", 0.0);
    }

    public Alumno(String dni, String nombre, double notaMedia) {
        setDni(dni);
        setNombre(nombre);
        setNotaMedia(notaMedia);
    }

    public Alumno(Alumno otro) {
        this.dni = otro.dni;
        this.nombre = otro.nombre;
        this.notaMedia = otro.notaMedia;
    }

    // 3. Getters
    public String getDni() {
        return dni;
    }

    public String getNombre() {
        return nombre;
    }

    public double getNotaMedia() {
        return notaMedia;
    }

    // 4. Setters con validación
    public void setDni(String dni) {
        if (dni == null || !dni.matches("\\d{8}[A-Z]")) {
            throw new IllegalArgumentException("DNI inválido");
        }
        this.dni = dni;
    }

    public void setNombre(String nombre) {
        if (nombre == null || nombre.isBlank()) {
            throw new IllegalArgumentException("El nombre no puede estar vacío");
        }
        this.nombre = nombre.trim();
    }

    public void setNotaMedia(double notaMedia) {
        if (notaMedia < 0 || notaMedia > 10) {
            throw new IllegalArgumentException("Nota fuera de rango [0-10]");
        }
        this.notaMedia = notaMedia;
    }

    // 5. Otros métodos de negocio
    public boolean estaAprobado() {
        return notaMedia >= 5;
    }

    @Override
    public String toString() {
        return String.format("Alumno[%s, %s, %.2f]", dni, nombre, notaMedia);
    }
}

6. Buenas prácticas resumidas

Recomendación Motivo
Declara los atributos private Evita modificaciones externas no controladas
Inicializa en constructores El objeto nace válido
Valida en setters y constructores Un solo punto de entrada para reglas de negocio
Usa final cuando el valor no cambia Aumenta seguridad y claridad
No generes setters de atributos calculados Ej. total = precio * cantidad → calcúlalo en getTotal()