4 minutos

La Palabra Clave final en Java

La Palabra Clave final en Java

1. Introducción

Si bien la herencia nos permite reutilizar código existente, a veces necesitamos establecer limitaciones a la extensibilidad por diversas razones; la palabra clave final nos permite hacer precisamente eso.

En este tutorial, examinaremos lo que significa la palabra clave final para clases, métodos y variables.

2. Clases final

Las clases marcadas como final no pueden ser extendidas. Si observamos el código de las bibliotecas centrales de Java, encontraremos muchas clases finales allí. Un ejemplo es la clase String.

Consideremos la situación si pudiéramos extender la clase String, sobrescribir cualquiera de sus métodos y sustituir todas las instancias de String con las instancias de nuestra subclase específica de String.

El resultado de las operaciones sobre objetos String se volvería entonces impredecible. Y dado que la clase String se utiliza en todas partes, eso sería inaceptable. Es por eso que la clase String está marcada como final.

Cualquier intento de heredar de una clase final provocará un error del compilador. Para demostrar esto, creemos la clase final Gato:

public final class Gato {
    private int peso;
    // getter y setter estándar
}

Y tratemos de extenderla:

public class GatoNegro extends Gato {
}

Veremos el error del compilador:

The type GatoNegro cannot subclass the final class Gato

Nota que la palabra clave final en una declaración de clase no significa que los objetos de esta clase sean inmutables. Podemos cambiar libremente los campos del objeto Gato:

Gato gato = new Gato();
gato.setPeso(1);

assertEquals(1, gato.getPeso());

Simplemente no podemos extenderla.

Si seguimos estrictamente las reglas de un buen diseño, debemos crear y documentar una clase con cuidado o declararla final por razones de seguridad. Sin embargo, debemos tener cuidado al crear clases finales.

Observa que hacer que una clase sea final significa que ningún otro programador puede mejorarla. Imagina que estamos utilizando una clase y no tenemos el código fuente de la misma, y hay un problema con uno de sus métodos.

Si la clase es final, no podemos extenderla para sobrescribir el método y solucionar el problema. En otras palabras, perdemos la extensibilidad, uno de los beneficios de la programación orientada a objetos.

3. Métodos final

Los métodos marcados como final no pueden ser sobrescritos (override). Cuando diseñamos una clase y sentimos que un método no debe ser sobrescrito, podemos hacer que este método sea final. También podemos encontrar muchos métodos finales en las bibliotecas centrales de Java.

A veces no necesitamos prohibir completamente la extensión de una clase, sino solo evitar la sobrescritura de algunos métodos. Un buen ejemplo de esto es la clase Thread. Es legal extenderla y así crear una clase de hilo personalizada. Pero su método isAlive() es final.

Este método comprueba si un hilo está vivo. Es imposible sobrescribir correctamente el método isAlive() por muchas razones. Una de ellas es que este método es nativo. El código nativo se implementa en otro lenguaje de programación y a menudo es específico del sistema operativo y el hardware en el que se ejecuta.

Creemos una clase Perro y hagamos que su método sonido() sea final:

public class Perro {
    public final void sonido() {
        // ...
    }
}

Ahora extendamos la clase Perro e intentemos sobrescribir su método sonido():

public class PerroNegro extends Perro {
    public void sonido() {
    }
}

Veremos el error del compilador:

- overrides 
com.javamagician.final.Perro.sonido
- Cannot override the final method from Perro
sonido() method is final and can’t be overridden

Si algunos métodos de nuestra clase son llamados por otros métodos, deberíamos considerar hacer que los métodos llamados sean final. De lo contrario, sobrescribirlos puede afectar el trabajo de los llamadores y causar resultados sorprendentes.

Si nuestro constructor llama a otros métodos, generalmente deberíamos declarar estos métodos como final por la razón mencionada anteriormente.

¿Cuál es la diferencia entre hacer que todos los métodos de la clase sean final y marcar la clase en sí como final? En el primer caso, podemos extender la clase y agregar nuevos métodos a ella.

En el segundo caso, no podemos hacerlo.

4. Variables final

Las variables marcadas como final no pueden ser reasignadas. Una vez que una variable final se inicializa, no se puede alterar.

4.1. Variables Primitivas final

Declaremos una variable primitiva final i, luego asignemos 0 a ella.

Y tratemos de asignarle un valor de 1:

public void cuandoAsignamosVariableFinal_entoncesSoloUnaVez() {
    final int i = 0;
    // ...
    i = 1;
}

El compilador dice:

The final local variable i may already have been assigned

4.2. Variables de Referencia final

Si tenemos una variable de referencia final, tampoco podemos reasignarla. Pero esto no significa que el objeto al que hace referencia sea inmutable. Podemos cambiar libremente las propiedades de este objeto.

Para demostrar esto, declaremos la variable de referencia final gato e inicialicémosla:

final Gato gato = new Gato();

Si intentamos reasignarla, veremos un error del compilador:

The final local variable gato cannot be assigned. It must be blank and not using a compound assignment

Pero podemos cambiar las propiedades de la instancia de Gato:

gato.setPeso(4);

assertEquals(4, gato.getPeso());

4.3. Campos final

Los campos final pueden ser constantes o campos de escritura única. Para distinguirlos, debemos hacernos una pregunta: ¿incluiríamos este campo si fuéramos a serializar el objeto? Si no, entonces no es parte del objeto, sino una constante.

Ten en cuenta que según las convenciones de nomenclatura, las constantes de clase deben estar en mayúsculas, con componentes separados por el carácter de subrayado ("_"):

static final int ANCHO_MAXIMO = 1337;

Ten en cuenta que cualquier campo final debe inicializarse antes de que el constructor se complete.

Para campos static final, esto significa que podemos inicializarlos:

  • al declararlos, como se muestra en el ejemplo anterior
  • en el bloque de inicialización estática

Para campos final de instancia, esto significa que podemos inicializarlos:

  • al declararlos
  • en el bloque de inicialización de instancia
  • en el constructor

De lo contrario, el compilador nos dará un error.

4.4. Argumentos final

La palabra clave final también es legal para ponerla antes de los argumentos de un método. Un argumento final no se puede cambiar dentro de un método:

public void metodoConArgumentosFinales(final int x) {
    x = 0;
}

La asignación anterior provoca el error del compilador:

The final local variable x cannot be assigned. It must be blank and not using a compound assignment

5. Conclusión

En este artículo, aprendimos lo que significa la palabra clave final para clases, métodos y variables. Aunque es posible que no utilicemos la palabra clave final con frecuencia en nuestro código interno, puede ser una buena solución de diseño.