</>waridocu

Java

Encapsulación: getters, setters y modificadores de acceso

Por qué los atributos se declaran private, cómo se escriben getters y setters de verdad, qué significa cada modificador de acceso y cuándo NO hace falta un setter.

javaencapsulaciongetterssettersprivatepublicpoo

El problema: un objeto que nadie protege

En Clases y objetos la primera versión de Alumno dejaba sus atributos sin ningún modificador:

Ejemplo: una clase sin protecciónjava
public class Alumno {
    String nombre;
    double nota;
}

Con eso, cualquier línea de cualquier parte del programa puede escribir:

javajava
Alumno a = new Alumno();
a.nota = -50;
a.nota = 12345;
a.nombre = null;

Nada de eso tiene sentido para un alumno real, y la clase Alumno no puede hacer absolutamente nada al respecto. El objeto queda en un estado inválido y el error se descubre mucho después, en otro archivo, cuando alguien intenta calcular un promedio y le sale un número absurdo.

La encapsulación resuelve esto invirtiendo quién manda: en vez de que el resto del programa escriba directamente en los atributos, la clase expone un conjunto controlado de métodos y se guarda para sí la única llave que toca los datos.

Nota

“Encapsular” es literalmente “meter en una cápsula”. La imagen es la de un medicamento: la cápsula tiene una superficie lisa y predecible con la que interactúas, y el contenido queda adentro, sin que puedas manipularlo directamente.

Los cuatro modificadores de acceso

Java tiene cuatro niveles de visibilidad. Se escriben delante del atributo o del método:

ModificadorQuién puede accederCuándo lo usarías
privateSolo la propia claseCasi todos los atributos. Es el que deberías usar por defecto.
(nada)Las clases del mismo paquete (package-private)Rara vez a propósito; suele ser un private que alguien olvidó escribir.
protectedEl mismo paquete y las subclasesCosas que una clase hija legítimamente necesita — ver Herencia.
publicCualquier clase, de cualquier paqueteLos métodos que forman la interfaz de uso de la clase.

La regla práctica cabe en una línea: atributos private, métodos que quieres que se usen desde afuera public.

Ejemplo: la misma clase, ahora encapsuladajava
public class Alumno {
    private String nombre;
    private double nota;
}

A partir de ese private, la línea a.nota = -50; escrita desde main ya no compila. El compilador dice algo como nota has private access in Alumno — y eso es una victoria, no un obstáculo: el error apareció al compilar, en la línea exacta que lo causa, en vez de convertirse en un número raro tres pantallas más adelante.

Getters: leer sin poder escribir

Un getter es un método public que devuelve el valor de un atributo private. La convención de nombres en Java es get + el nombre del atributo en CamelCase:

javajava
public class Alumno {
    private String nombre;
    private double nota;

    public String getNombre() {
        return nombre;
    }

    public double getNota() {
        return nota;
    }
}

Cada getter no recibe parámetros (() vacío), devuelve el tipo del atributo, y su cuerpo es un único return. Desde afuera se usa así:

javajava
Alumno a = new Alumno("Ana", 8.5);
System.out.println(a.getNombre() + " sacó " + a.getNota());
Nota

Para un atributo boolean la convención cambia el prefijo: se usa is en vez de get, porque hace que la condición se lea como una frase. if (alumno.isAprobado()) se lee mucho mejor que if (alumno.getAprobado()).

Setters: escribir bajo condiciones

Un setter es el camino de vuelta: un método public que recibe un valor nuevo y lo asigna al atributo, pero solo si pasa las validaciones que la clase decida. La convención es set + el nombre, no devuelve nada (void) y recibe exactamente un parámetro del tipo del atributo:

javajava
public void setNota(double nuevaNota) {
    if (nuevaNota < 0 || nuevaNota > 10) {
        System.out.println("Nota inválida: " + nuevaNota);
        return;
    }
    this.nota = nuevaNota;
}

Aquí está la diferencia real con un atributo público: a.nota = -50 no tenía forma de fallar; a.setNota(-50) tiene un lugar donde poner el if. Ese if es toda la razón de existir del setter.

Advertencia

Un setter que solo hace this.x = x; sin validar nada no protege nada: es un atributo público con dos líneas más de código. No está mal escribirlo — a veces se hace porque una librería o un framework espera encontrar esa forma — pero no confundas escribir el setter con haber encapsulado algo.

La clase completa

Ejemplo: Alumno encapsulado de punta a puntajava
public class Alumno {
    private String nombre;
    private double nota;

    public Alumno(String nombre, double nota) {
        this.nombre = nombre;
        setNota(nota);
    }

    public String getNombre() {
        return nombre;
    }

    public double getNota() {
        return nota;
    }

    public void setNota(double nuevaNota) {
        if (nuevaNota >= 0 && nuevaNota <= 10) {
            this.nota = nuevaNota;
        }
    }

    public boolean isAprobado() {
        return nota >= 6;
    }
}

Fíjate en dos detalles que suelen pasarse por alto:

  1. El constructor llama a setNota(nota) en vez de asignar directo. Si la validación vive en el setter, dejar que el constructor la esquive abre justamente el agujero que estabas tapando: new Alumno("Ana", -50) volvería a crear un objeto inválido.
  2. isAprobado() no tiene ningún atributo detrás. No todo método public es el getter de algo: este calcula un dato a partir del estado interno. Guardar un atributo aprobado además de nota sería peor, porque habría que acordarse de mantenerlo sincronizado cada vez que la nota cambie.

No todo atributo necesita los dos

Escribir mecánicamente un getter y un setter para cada atributo es el error más común al aprender encapsulación. La pregunta correcta es “¿qué necesita el resto del programa hacer con este dato?”:

SituaciónQué exponer
El valor se lee mucho y no debe cambiar nunca después de crear el objetoSolo getter (por ejemplo, el DNI o el legajo)
El valor se lee y se modifica durante la vida del objetoGetter + setter con validación
El valor es un detalle interno de cómo la clase hace su trabajoNinguno de los dos
El valor solo se configura, nunca se consulta desde afueraSolo setter (poco frecuente, pero existe)
Importante

Empieza siempre por lo más cerrado: todo private, ningún método público. Después abre lo que el programa realmente necesita. Es fácil agregar un getter después; sacar uno que media docena de archivos ya usa es mucho más caro.

Pon a prueba

🎯 ¿Qué está mal en esta clase?
javajava
public class Cuenta {
    private String titular;
    private double saldo;

    public Cuenta(String titular, double saldo) {
        this.titular = titular;
        this.saldo = saldo;
    }

    public String getTitular() {
        return titular;
    }

    public double getSaldo() {
        return saldo;
    }

    public void setSaldo(double nuevoSaldo) {
        if (nuevoSaldo >= 0) {
            this.saldo = nuevoSaldo;
        }
    }
}
  • Eso es exactamente lo que se busca: el atributo cerrado y una puerta controlada para leerlo. Un getter público sobre un atributo privado es el patrón normal, no un error.

  • Correcto. new Cuenta("Ana", -500) crea una cuenta con saldo negativo sin que el if de setSaldo llegue a ejecutarse nunca. La validación tiene que estar en el único camino por el que se puede escribir el atributo, y el constructor es un camino más.

  • El titular de una cuenta es justamente un dato que normalmente NO debería poder cambiarse desde afuera. Que falte un setter es una decisión de diseño válida, no un defecto.

  • Podría hacerlo, y a veces es útil para avisar si la operación se aplicó, pero es una preferencia de estilo. No es lo que hace que esta clase quede en un estado inválido.

✏️ Completa el getter y el setter

La clase Producto guarda un precio que nunca puede ser negativo. Escribe el modificador de acceso del atributo, el nombre de cada método según la convención de Java, y la palabra que distingue el atributo del parámetro.

public class Producto {
   double precio;

  public double () {
      return precio;
  }

  public  setPrecio(double precio) {
      if (precio >= 0) {
          .precio = precio;
      }
  }
}

💡 Predice la salida

Con la clase Alumno completa de más arriba (la que llama a setNota desde el constructor), ¿qué imprime esto?

javajava
Alumno a = new Alumno("Ana", -3);
System.out.println(a.getNota());
System.out.println(a.isAprobado());
a.setNota(7.5);
System.out.println(a.isAprobado());

Imprime 0.0, después false, y después true.

La primera línea es la que sorprende: new Alumno("Ana", -3) llama a setNota(-3), que rechaza el valor y no asigna nada. Como nota es un double que nunca llegó a asignarse, se queda con su valor por defecto, que en Java es 0.0 — los atributos, a diferencia de las variables locales, se inicializan solos.

Con nota en 0.0, isAprobado() devuelve false. Después setNota(7.5) sí pasa la validación, la nota queda en 7.5, y isAprobado() pasa a true.

Esto muestra el límite de validar en silencio: el objeto quedó válido, pero con un valor que nadie pidió y sin que nadie se enterara del rechazo. Un diseño más estricto cortaría ahí mismo lanzando una excepción — ver Excepciones.

🧩 Ordena el código

Ordena las líneas para escribir una clase Termostato con un atributo privado y un setter que limite la temperatura entre 15 y 30.

  1. if (nueva >= 15 && nueva <= 30) {
  2. }
  3. public int getTemperatura() {
  4. }
  5. }
  6. public class Termostato {
  7. return temperatura;
  8. this.temperatura = nueva;
  9. private int temperatura;
  10. }
  11. public void setTemperatura(int nueva) {

Siguiente paso

Ya sabes construir una clase que protege sus propios datos. El paso siguiente es que una clase pueda partir de otra en vez de escribirse desde cero, reutilizando sus atributos y métodos y cambiando solo lo que la diferencia — ver Herencia y @Override.