Palabra clave final en Java
Haz variables constantes, impide la sobreescritura de métodos y prohíbe la herencia de clases con la palabra clave final en Java.
final significa "establecer esto una vez y no cambiarlo." Se aplica a cuatro elementos, y el efecto depende de cuál sea:
- En una variable, la referencia no puede reasignarse.
- En un método, el método no puede ser sobreescrito por una subclase.
- En una clase, la clase no puede extenderse.
- En un parámetro, el parámetro no puede reasignarse dentro del método.
El hilo conductor es "sin modificación posterior tras la asignación inicial." Lo que exactamente se prohíbe depende de lo que se está marcando.
Variables locales finales
La forma más simple: una variable local final no puede reasignarse después de su primera asignación.
void demo() {
final int max = 100;
max = 50; // ERROR: cannot assign a value to final variable max
}No es obligatorio inicializarla en la declaración — puedes diferir la primera asignación, siempre que la asignes exactamente una vez antes de cualquier lectura:
final int x;
if (condition) x = 1;
else x = 2;
System.out.println(x); // ok — x was definitely assignedfinal en una variable local es principalmente una herramienta de documentación — le dice a un lector futuro (y al compilador) "esto no cambiará a partir de este punto." Las capturas en lambdas y clases internas lo requieren: cualquier variable local usada dentro de ellas debe ser final o efectivamente final (nunca reasignada).
Parámetros finales
Un parámetro final no puede reasignarse dentro del cuerpo del método:
void greet(final String name) {
name = name.toUpperCase(); // ERROR
}Algunos equipos exigen final en cada parámetro; otros lo consideran ruido visual. Cualquiera de las dos opciones está bien — lo que importa es la consistencia dentro de una base de código. El hábito de mutar parámetros que esto desincentiva es una fuente real de errores.
Campos finales
Un campo final se asigna exactamente una vez — ya sea en la declaración, en un inicializador de instancia o en el constructor — y nunca más:
public class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x; // assigned once
this.y = y;
}
// no setX or setY — there's no way to change the values
}Este es el núcleo de una clase inmutable. Cada campo es final; nada puede mutar el objeto tras la construcción.
El compilador verifica que cada campo final se asigne exactamente una vez en cada camino del constructor. Si falta un camino, se produce un error de compilación. Si se asigna dos veces, también hay un error de compilación.
static final — constantes
La forma estándar de una constante es public static final más un nombre en UPPER_SNAKE:
public static final double PI = 3.141592653589793;
public static final int MAX_RETRY = 3;Estático, porque no hay razón para mantener una copia por instancia; final, porque es una constante. Para constantes de tipo primitivo y String, el compilador inserta el valor directamente en cada punto de llamada. Una consecuencia: si cambias el valor de dicha constante, las clases que lo referenciaban conservan el valor antiguo hasta que se recompilen — el literal fue incorporado en su bytecode en el momento de su propia compilación.
Final y referencias
Un malentendido común: final congela la referencia, no el objeto al que apunta. Con un array o lista final, aún puedes mutar el contenido:
final int[] nums = {1, 2, 3};
nums[0] = 99; // ok — mutating the array, not reassigning the reference
nums = new int[5]; // ERROR — reassigning the reference
final List<String> names = new ArrayList<>();
names.add("Rex"); // ok — mutates the list
names = List.of(); // ERRORSi quieres una lista cuyo contenido también esté congelado, envuélvela con List.copyOf(...) o Collections.unmodifiableList(...).
Métodos finales
final en un método significa que ninguna subclase puede sobreescribirlo:
public class Shape {
public final String describe() { // can never be overridden
return getClass().getSimpleName() + " area=" + area();
}
public double area() { return 0; }
}
public class Circle extends Shape {
public String describe() { return "circle"; } // ERROR
}Marcarías un método como final cuando sobreescribirlo rompería invariantes de las que depende la clase. Es una decisión de diseño con criterio — la mayoría de los métodos no son final en bases de código reales — pero para plantillas que no deben cambiar, es la herramienta adecuada.
Clases finales
final en una clase significa que ninguna clase puede extenderla:
public final class Money { ... }
public class CryptoMoney extends Money { } // ERRORÚsalo cuando la extensión destruiría el diseño. La biblioteca estándar lo hace habitualmente: String, Integer, Long, Double y el resto de los envoltorios de tipos primitivos son todos final — extenderlos permitiría introducir comportamiento mutable en un tipo que el lenguaje trata como inmutable.
Final no es inmutabilidad
Un campo final por sí solo no hace que un objeto sea inmutable; simplemente evita que la referencia cambie. La inmutabilidad real requiere:
- Cada campo
final. - La clase misma
final, o construida con cuidado para evitar que las subclases añadan estado mutable. - Ningún método que exponga un objeto interno mutable (copias defensivas al devolverlos).
- Ningún método que permita a quien llama mutar el estado a través de él.
Los records (cubiertos en records) agrupan la mayor parte de esto automáticamente.
Un ejemplo completo
Qué viene a continuación
Los campos private y la inmutabilidad con final son los bloques de construcción de la siguiente idea: ocultar el estado completamente detrás de los métodos de una clase. El capítulo sobre encapsulación une todos esos hilos.