Getters y setters en Java
Accede de forma segura a campos privados en Java con métodos getter y setter, y añade validación en los setters.
Un getter es un método que devuelve el valor de un campo; un setter es un método que escribe en uno. Juntos son la forma estándar de dar acceso controlado a un campo privado. El capítulo anterior sobre encapsulación explicó el por qué — este capítulo trata el cómo, incluyendo las convenciones de nomenclatura, los patrones de validación y cuándo conviene omitirlos por completo.
El patrón básico
Un campo, un getter y un setter:
public class User {
private String email;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}Por convención:
- El getter se llama
get<FieldName>para todo excepto los booleanos. - Para un campo
boolean, el getter se llamais<FieldName>(isActive,hasPermission). - El setter se llama
set<FieldName>y acepta un parámetro del tipo del campo. - Ambos son
publicsalvo que haya una razón para lo contrario.
Estas reglas también son la convención JavaBeans — muchas herramientas (bibliotecas de serialización, motores de plantillas, refactorizaciones de IDE) dependen de ellas y simplemente no verán los campos cuyos accesores tengan nombres distintos.
Validación en los setters
Un setter es tu única oportunidad de rechazar una entrada incorrecta antes de que se escriba el campo:
public class User {
private String email;
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("not a valid email: " + email);
}
this.email = email;
}
}Un setter vacío que no hace más que asignar no es mucho mejor que un campo público. El objetivo de poner un setter entre los llamadores y el campo es poder colocar ahí una comprobación. Si te encuentras escribiendo docenas de setters que todos se leen como this.x = x;, pregúntate si la clase realmente necesita ser mutable — un objeto con constructor y solo getters (una clase inmutable o un record) suele ser una mejor opción.
Getters calculados y derivados
Un getter no tiene que mapear uno a uno con un campo. Puede calcular su valor de retorno:
public class Rectangle {
private final double width, height;
public Rectangle(double w, double h) { this.width = w; this.height = h; }
public double getWidth() { return width; }
public double getHeight() { return height; }
public double getArea() { return width * height; } // derived
}Para quien llama, getArea() se ve exactamente igual que getWidth() — ambos son métodos que devuelven un double. El hecho de que uno lea un valor almacenado y el otro lo calcule es un detalle de implementación que puedes cambiar sin que nadie lo note.
Getters de solo lectura
Un getter sin setter expone un campo para lectura pero lo bloquea para escritura:
public class Order {
private final long id;
private final long createdAt;
public Order(long id, long createdAt) {
this.id = id;
this.createdAt = createdAt;
}
public long getId() { return id; }
public long getCreatedAt() { return createdAt; }
}El código externo puede preguntar "¿cuál es el ID?" pero no puede modificarlo. Así es como funcionan en la práctica los campos inmutables tras la construcción.
Nomenclatura booleana: is, has, should
Los getters booleanos se leen de forma más natural con un prefijo is/has/can/should:
public boolean isActive() { return active; }
public boolean hasPermission() { return permission != null; }
public boolean canRetry() { return retries < maxRetries; }Combinado con el nombre del campo, el punto de llamada se lee como una oración en inglés: if (user.isActive()) { ... }. El setter para estos es simplemente setActive(boolean), sin el is.
Un detalle a tener en cuenta: las herramientas JavaBeans derivan el nombre de la propiedad del accesor, por lo que isActive() y getActive() se asignan ambos a la propiedad active — pero las herramientas generalmente buscan accesores con prefijo is solo en el tipo primitivo boolean. Para un campo Boolean encuadrado, usa getActive() para seguir siendo reconocible.
Copias defensivas (de nuevo)
Si un getter fuera a devolver un objeto mutable interno, devuelve una copia o una vista no modificable:
private final List<String> tags = new ArrayList<>();
public List<String> getTags() {
return List.copyOf(tags);
}Lo mismo para un setter en la dirección de entrada:
public void setTags(List<String> tags) {
this.tags.clear();
this.tags.addAll(tags); // copy in
}Sin estas copias, el llamador podría mutar la lista interna tags y saltarse todo lo demás que hace la clase.
Java moderno: accesores más cortos
El código Java más reciente (especialmente en bibliotecas que no están ligadas a las herramientas JavaBeans) a menudo omite el prefijo get por simetría con la forma en que se nombran los accesores de record:
public class Point {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
}Si no estás limitado por JavaBeans (sin Hibernate, sin configuración predeterminada de Jackson, sin JSP), este estilo está bien — y coincide con lo que record Point(int x, int y) {} generaría automáticamente. Elige un estilo por base de código y aplícalo de forma consistente. Para una visión completa del enfoque de estilo record, consulta records modernos.
No los generes por reflejo
Los IDE modernos pueden generar con gusto un getter y un setter para cada campo con una sola tecla. Resiste el impulso. Cada par generado es una decisión de diseño que estás tomando:
- Un getter expone un campo — ¿realmente quieres que los llamadores lo lean?
- Un setter expone una ruta de escritura — ¿realmente quieres que los llamadores lo modifiquen?
- Si ambas respuestas son incondicionalmente sí, ¿por qué el campo es privado?
La respuesta correcta suele ser "ninguna" — reemplaza setBalance(int) con deposit(int) y withdraw(int) que expresan las operaciones reales.
Un ejemplo práctico
Qué viene a continuación
Eso concluye los fundamentos de una única clase — cómo declararla, cómo controlar sus miembros y cómo exponer solo lo necesario al exterior. El próximo capítulo comienza la segunda gran idea de POO: la herencia, donde una clase se construye sobre otra en lugar de empezar desde cero. Continúa con herencia en Java.