Getters y setters o atributos públicos en Java, ¿qué es mejor?

Cuando trabajamos con clases en Java, es común preguntarse qué ventajas reales tienen los getters y setters frente a simplemente declarar atributos públicos. A primera vista, puede parecer que acceder directamente a un atributo público es más sencillo y directo, pero al profundizar descubrimos que los getters y setters ofrecen un control mucho más avanzado y flexible sobre cómo se manipulan los datos dentro de una clase.

Los getters y setters son métodos que nos permiten acceder y modificar atributos privados de una clase de forma controlada. Por ejemplo, si tenemos un atributo privado nombre, creamos un método getNombre para recuperar su valor y un método setNombre para asignarle uno nuevo. Esto implica que desde fuera de la clase no podemos acceder directamente al atributo, sino que debemos usar estos métodos, lo que es la base de la encapsulación en programación orientada a objetos.

Una ventaja fundamental de esta encapsulación es que podemos controlar qué sucede dentro de esos métodos sin afectar al código que los utiliza. Por ejemplo, si el atributo nombre fuera público, podríamos asignarle cualquier valor, incluso nulo o vacío, lo que podría ser inválido para nuestra lógica de negocio. Sin embargo, con un setter podemos incluir validaciones que impidan asignar valores no deseados, como cadenas vacías o nulas, lanzando una excepción o mostrando un error cuando se intente hacer una asignación incorrecta.

public class Persona {
    private String nombre;

    public String getNombre() {
        return nombre;
    }

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

Además, dentro de los getters y setters podemos modificar la forma en que se recuperan o asignan los datos sin que el resto del código se entere. Por ejemplo, podemos hacer que el getter devuelva el nombre con la primera letra en mayúscula y el resto en minúscula, asegurando que siempre se presenta de forma correcta, aunque internamente el atributo se almacene de otra manera.

public String getNombre() {
    if (nombre == null || nombre.isEmpty()) {
        return nombre;
    }
    String inicial = nombre.substring(0, 1).toUpperCase();
    String resto = nombre.substring(1).toLowerCase();
    return inicial + resto;
}

Esta flexibilidad es especialmente útil cuando el código evoluciona. Supongamos que en lugar de almacenar el nombre directamente en un atributo, queremos obtenerlo desde un directorio externo o una base de datos. Si usamos atributos públicos, tendríamos que cambiar todas las partes del código que acceden directamente a ese atributo, lo cual puede ser complicado o incluso imposible si el código está distribuido o fuera de nuestro control. En cambio, si usamos getters y setters, solo necesitamos modificar la implementación interna de esos métodos para que obtengan o guarden la información desde la nueva fuente, sin que el resto del código tenga que cambiar.

Un ejemplo aún más sofisticado es el uso de propiedades virtuales, donde ni siquiera existe un atributo interno que almacene el dato. Por ejemplo, podemos crear un getter y un setter para un apellido que, en lugar de guardar el valor en un atributo, lo escriba y lea desde un archivo en disco. Así, cuando llamamos a setApellido, se escribe el apellido en un archivo, y cuando llamamos a getApellido, se lee desde ese archivo.

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;

public class Persona {
    private static final Path archivoApellido = Paths.get("apellido.txt");

    public String getApellido() {
        try {
            return Files.readString(archivoApellido);
        } catch (IOException e) {
            return "";
        }
    }

    public void setApellido(String apellido) {
        try {
            Files.writeString(archivoApellido, apellido);
        } catch (IOException e) {
            // Manejo de error simplificado
            e.printStackTrace();
        }
    }
}

Desde fuera, esta propiedad apellido se comporta como cualquier otra propiedad de la clase, pero internamente está realizando operaciones de entrada/salida. Esto demuestra cómo los getters y setters pueden encapsular comportamientos complejos y ocultar detalles de implementación, haciendo que el código sea más flexible y mantenible.

En definitiva, los getters y setters no solo sirven para proteger atributos privados, sino que nos permiten validar datos, modificar su presentación, cambiar la fuente de los datos sin afectar a quien usa la clase y crear propiedades virtuales con comportamientos personalizados. Todo esto es imposible o muy complicado de lograr si usamos atributos públicos directamente. Por eso, aunque a veces parezca más sencillo acceder directamente a los atributos, usar getters y setters es una práctica que aporta robustez y escalabilidad a nuestro código en Java.

Lista de reproducción
  1. 1
    ¿Cómo funciona Comparator en Java?
    10 minutos
  2. 2
    ¿Para qué sirve Override?
    4 minutos
  3. 3
    Cómo usar la clase Properties en Java
    14 minutos
  4. 4
    Interfaces funcionales y funciones flecha
    8 minutos
  5. 5
    Bloque static de Java: inicializadores estáticos y otros usos
    7 minutos
  6. 6
    import static, ¿para qué sirve?
    3 minutos
  7. 7
    Uso de Optional en Java
    10 minutos
  8. 8
    Introducción al Scanner de Java
    11 minutos
  9. 9
    ¿Para qué sirve el modificador static de Java?
    6 minutos
  10. 10
    Funciones variádicas en Java
    6 minutos
  11. 11
    Inner classes en Java
    8 minutos
  12. 12
    var en Java: una introducción para gente nueva
    7 minutos
  13. 13
    Switch expressions en Java
    10 minutos
  14. 14
    Java: del instanceof al Pattern Matching
    7 minutos
  15. 15
    Sealed classes en Java
    8 minutos
  16. 16
    Getters y setters o atributos públicos en Java, ¿qué es mejor?
    9 minutos
  17. 17
    El String[] args del método main de Java
    8 minutos
  18. 18
    Introducción a clases anónimas en Java
    6 minutos
  19. 19
    Introducción a enum en Java
    6 minutos