En el lenguaje de programación Java , la finalpalabra clave se utiliza en varios contextos para definir una entidad que solo se puede asignar una vez.
Una vez finalque se asigna un valor a una variable, se espera que siempre contenga el mismo valor. Si una finalvariable contiene una referencia a un objeto, el estado de este puede modificarse mediante operaciones sobre él, pero la variable siempre hará referencia al mismo objeto (esta propiedad finalse denomina no transitividad [ 1 ] ). Esto también se aplica a los arreglos, ya que estos son objetos; si una finalvariable contiene una referencia a un arreglo, los componentes del arreglo pueden modificarse mediante operaciones sobre él, pero la variable siempre hará referencia al mismo arreglo [ 2 ] .
Clases finales
Una clase final no puede tener subclases. Dado que esto puede brindar beneficios de seguridad y eficiencia, muchas de las clases de la biblioteca estándar de Java son finales, como java.lang.Systemy java.lang.String.
Ejemplo:
clase final Final { // ... }// Clase prohibida Derived extiende Final { // ... }Métodos finales
Un método final no puede ser sobrescrito ni ocultado por subclases. [ 3 ] Esto se utiliza para evitar un comportamiento inesperado cuando una subclase modifica un método que puede ser crucial para la función o la consistencia de la clase. [ 4 ]
Ejemplo:
public class Base { public void foo () { // ... }public final void bar () { // ... }public static void baz () { // ... }public static final void qux () { // ... } }public class Derived extends Base { // OK, sobrescribiendo Base::foo() @Override public void foo () { // ... }// prohibido @Override public void bar () { // ... }// OK, sobrescribiendo Base::baz() @Override public static void baz () { // ... }// prohibido @Override public static void qux () { // ... } }Una idea errónea común es que declarar un método como `inline` finalmejora la eficiencia al permitir que el compilador lo inserte directamente dondequiera que se llame (véase expansión en línea ). Dado que el método se carga en tiempo de ejecución , los compiladores no pueden hacer esto. Solo el entorno de ejecución y el compilador JIT saben exactamente qué clases se han cargado, por lo que solo ellos pueden tomar decisiones sobre cuándo realizar la expansión en línea, independientemente de si el método es final o no. [ 5 ]
Los compiladores de código máquina que generan código máquina específico de la plataforma y ejecutable directamente son una excepción. Al usar el enlace estático , el compilador puede asumir con seguridad que los métodos y variables computables en tiempo de compilación pueden insertarse en línea.
Variables finales
Una variable final solo puede inicializarse una vez, ya sea mediante un inicializador o una instrucción de asignación. No es necesario inicializarla en el momento de la declaración: esto se denomina variable "final vacía". Una variable de instancia final vacía de una clase debe asignarse definitivamente en cada constructor de la clase en la que se declara; de forma similar, una variable estática final vacía debe asignarse definitivamente en un inicializador estático de la clase en la que se declara; de lo contrario, se produce un error de compilación en ambos casos. [ 6 ] (Nota: Si la variable es una referencia, esto significa que no se puede volver a enlazar para que haga referencia a otro objeto. Pero el objeto al que hace referencia sigue siendo mutable , si originalmente lo era).
A diferencia del valor de una constante , el valor de una variable final no necesariamente se conoce en tiempo de compilación. Se considera una buena práctica representar las constantes finales en mayúsculas, utilizando guiones bajos para separar las palabras. [ 7 ]
Ejemplo:
public class Sphere { // Pi es una constante universal, tan constante como puede ser cualquier cosa. public static final double PI = 3.141592653589793 ;public final double radio ; public final double xPos ; public final double yPos ; public final double zPos ;Esfera ( doble x , doble y , doble z , doble r ) { radio = r ; xPos = x ; yPos = y ; zPos = z ; }// ... }Cualquier intento de reasignar radius, xPos, yPos, o zPosdará como resultado un error de compilación. De hecho, incluso si el constructor no establece una variable final, intentar establecerla fuera del constructor dará como resultado un error de compilación .
Para ilustrar que la finalidad no garantiza la inmutabilidad: supongamos que reemplazamos las tres variables de posición por una sola:
Posición final pública pos ;donde poses un objeto con tres propiedades pos.x, pos.yy pos.z. Entonces posno se le puede asignar nada, pero las tres propiedades sí, a menos que sean finales en sí mismas.
Al igual que la inmutabilidad total , el uso de variables finales ofrece grandes ventajas, especialmente en la optimización. Por ejemplo, Sphereprobablemente habrá una función que devuelva su volumen; saber que su radio es constante nos permite memorizar el volumen calculado. Si tenemos relativamente pocos Sphereobjetos y necesitamos sus volúmenes con mucha frecuencia, la mejora en el rendimiento podría ser sustancial. Hacer que el radio de un objeto Spherefinalinforme a los desarrolladores y compiladores de que este tipo de optimización es posible en todo el código que utiliza Sphereobjetos.
Aunque parezca contravenir el finalprincipio, la siguiente es una declaración legal:
for ( final SomeObject obj : someList ) { // hacer algo con obj }Dado que la objvariable queda fuera de ámbito con cada iteración del bucle, en realidad se vuelve a declarar en cada iteración, lo que permite que el mismo token (es decir, obj) se utilice para representar múltiples variables. [ 8 ]
Variables finales en objetos anidados
Las variables finales se pueden usar para construir árboles de objetos inmutables. Una vez construidos, se garantiza que estos objetos no cambiarán. Para lograr esto, una clase inmutable solo debe tener campos finales, y estos campos finales solo pueden tener tipos inmutables. Los tipos primitivos de Java son inmutables, al igual que las cadenas y varias otras clases.
Si la construcción anterior se viola al tener un objeto en el árbol que no es inmutable, no se espera que nada accesible a través de la variable final sea constante. Por ejemplo, el siguiente código define un sistema de coordenadas cuyo origen siempre debe estar enEl origen se implementa mediante una java.awt.Pointclase, y esta clase define sus campos como públicos y modificables. Esto significa que, incluso al acceder al originobjeto a través de una ruta de acceso con solo variables finales, dicho objeto aún puede modificarse, como lo demuestra el siguiente ejemplo de código.
import java.awt.Point ;clase pública FinalDemo {clase estática Sistema de coordenadas { privado final Punto ORIGEN = nuevo Punto ( 0 , 0 );public Point getOrigin () { return ORIGIN ; } }public static void main ( String [] args ) { CoordinateSystem coordinateSystem = new CoordinateSystem ();sistemaCoordenadas.obtenerOrigen ( ) . x = 15 ;assert coordinateSystem.getOrigin ( ). getX ( ) == 0 ; } }La razón de esto es que declarar una variable como final solo significa que esta variable apuntará al mismo objeto en cualquier momento. Sin embargo, el objeto al que apunta la variable no se ve afectado por esa variable final. En el ejemplo anterior, el origenyLas coordenadas se pueden modificar libremente.
Para evitar esta situación indeseable, un requisito común es que todos los campos de un objeto inmutable sean finales y que los tipos de estos campos también sean inmutables. Esto impide que ciertas clases java.util.Date, como java.awt.Point`final`, se utilicen en objetos inmutables.
Clases finales e internas
Cuando se define una clase internafinal anónima dentro del cuerpo de un método, todas las variables declaradas en el ámbito de dicho método son accesibles desde la clase interna. Para valores escalares, una vez asignado el valor de la finalvariable, este no puede modificarse. Para valores de objeto, la referencia no puede cambiar. Esto permite al compilador de Java "capturar" el valor de la variable en tiempo de ejecución y almacenar una copia como un campo en la clase interna. Una vez que el método externo ha finalizado y su marco de pila se ha eliminado, la variable original desaparece, pero la copia privada de la clase interna persiste en la memoria de la propia clase.
paquete org.wikipedia.examples ;import javax.swing.JButton ; import javax.swing.JFrame ; import javax.swing.SwingUtilities ;public class FooGUI { public static void main ( String [] args ) { // inicializar componentes de la interfaz gráfica final JFrame jf = new JFrame ( "Hello world!" ); // permite acceder a jf desde el cuerpo de la clase interna jf . add ( new JButton ( "Click me" ));// Empaquetar y hacer visible en el hilo de despacho de eventos SwingUtilities.invokeLater ( new Runnable ( ) { @Override public void run () { jf.pack ( ) ; // Esto sería un error de compilación si jf no fuera final jf.setLocationRelativeTo ( null ) ; jf.setVisible ( true ) ; } }) ; } }Final en blanco
La variable final en blanco , introducida en Java 1.1, es una variable final cuya declaración carece de un inicializador. [ 9 ] [ 10 ] Antes de Java 1.1, una variable final requería un inicializador. Una variable final en blanco, por definición de "final", solo puede asignarse una vez; es decir, debe liberarse cuando se produce una asignación. Para ello, un compilador de Java realiza un análisis de flujo para asegurar que, para cada asignación a una variable final en blanco, la variable se libere definitivamente antes de la asignación; de lo contrario, se produce un error de compilación. [ 11 ]
final boolean hasTwoDigits ; if ( number >= 10 && number < 100 ) { hasTwoDigits = true ; } if ( number > -100 && number <= -10 ) { // error de compilación porque la variable final podría ya estar asignada. hasTwoDigits = true ; }Además, también se debe asignar definitivamente un final en blanco antes de poder acceder a él. [ 11 ]
booleano final es par ;Si ( número % 2 == 0 ) { es par = verdadero ; }// Error de compilación porque la variable no se asignó en el caso else. System.out.println ( isEven );Sin embargo, tenga en cuenta que una variable local no final también necesita ser asignada definitivamente antes de poder ser accedida. [ 11 ]
booleano esPar ; // NO finalSi ( número % 2 == 0 ) { es par = verdadero ; }// Mismo error de compilación porque la variable no final no se asignó en el caso else. System.out.println ( isEven );Reflexión
Aunque los campos finales inicializados están diseñados para ser inmutables, en realidad pueden modificarse mediante reflexión. El java.lang.reflectpaquete contiene métodos que permiten acceder a cualquier campo y modificarlo. Se prevé eliminar esta funcionalidad en una futura versión de Java en 2026. [ 12 ]
Análogo en C/C++ de las variables finales
En C y C++ , la construcción análoga es la constpalabra clave . Esto difiere sustancialmente de finalen Java, principalmente en que es un calificador de tipo : constes parte del tipo , no solo parte del identificador (variable). Esto también significa que la constancia de un valor puede cambiarse mediante conversión explícita de tipo , en este caso conocida como "const casting". Sin embargo, convertir la constancia y luego modificar el objeto resulta en un comportamiento indefinido si el objeto fue declarado originalmente como const. Java finales una regla estricta de tal manera que es imposible compilar código que rompa o eluda directamente las restricciones finales. Sin embargo, usando reflexión , a menudo es posible modificar variables finales. Esta característica se usa principalmente al deserializar objetos con miembros finales.
Además, debido a que C y C++ exponen punteros y referencias directamente, existe una distinción entre si el puntero en sí es constante y si los datos a los que apunta el puntero son constantes. Aplicar `const` consta un puntero en sí, como en `<const>` SomeClass* const ptr, significa que el contenido al que se hace referencia puede modificarse, pero la referencia en sí no (sin conversión de tipo). Este uso da como resultado un comportamiento que imita el comportamiento de una finalreferencia a una variable en Java. Por el contrario, cuando se aplica `const` solo a los datos referenciados, como en ` const SomeClass* ptr<const>`, el contenido no puede modificarse (sin conversión de tipo), pero la referencia en sí sí. Tanto la referencia como el contenido al que se hace referencia pueden declararse como `const` const.
En C++, la finalpalabra clave se utiliza para indicar que una función no puede ser sobrescrita. También se usa de forma similar a Java para declarar una clase como final (no se puede extender).
// final en una declaración de clase declara que una clase no puede ser extendida class Z final : public X , public Y { public : // final en la firma de un método declara que un método no puede ser sobrescrito más void someOperation () override final { // hacer algo aquí } };Análogos de C# para la palabra clave final
C# puede considerarse similar a Java en términos de sus características de lenguaje y sintaxis básica: Java tiene JVM, C# tiene .Net Framework ; Java tiene bytecode , C# tiene CIL ; Java no tiene soporte para punteros (memoria real), C# es igual.
En cuanto a la última palabra clave, C# tiene dos palabras clave relacionadas:
- La palabra clave equivalente para métodos y clases es
sealed - La palabra clave equivalente para variables es
readonly[ 13 ].
Tenga en cuenta que una diferencia clave entre la palabra clave derivada de C/C++ consty la palabra clave de C# readonlyes que constse evalúa en tiempo de compilación, mientras que readonlyse evalúa en tiempo de ejecución, y por lo tanto puede tener una expresión que solo se calcula y se corrige más tarde (en tiempo de ejecución).
Véase también
Referencias
- ↑ Coblenz, Michael; Sunshine, Joshua; Aldrich, Jonathan; Myers, Brad; Weber, Sam; Shull, Forrest (14–22 de mayo de 2016). "Explorando el soporte de lenguaje para la inmutabilidad". La 38.ª Conferencia Internacional sobre Ingeniería de Software .
- ↑ Especificación del lenguaje Java #4.12.4
- ↑ "Capítulo 8. Clases" . docs.oracle.com . Consultado el 25 de abril de 2024 .
- ↑ "Cómo escribir clases y métodos finales" . docs.oracle.com . Consultado el 25 de abril de 2024 .
- ↑ "Teoría y práctica de Java: ¿Es esa tu respuesta definitiva?" . developer.ibm.com . Archivado del original el 8 de febrero de 2009 . Consultado el 25 de abril de 2024 .
- ↑ Especificación del lenguaje Java #8.3.1.2.
- ↑ "Directrices de estilo de programación en Java" . petroware.no . Consultado el 25 de abril de 2024 .
- ↑ Pattis, Richard E. "Más Java" . Programación avanzada/Práctica 15–200 . Escuela de Ciencias de la Computación, Universidad Carnegie Mellon . Consultado el 23 de julio de 2010 .
- ↑ Flanagan, David (mayo de 1997). «Capítulo 5 Clases internas y otras nuevas características del lenguaje: 5.6 Otras nuevas características de Java 1.1». Java en pocas palabras (2.ª ed.). O'Reilly. ISBN 1-56592-262-X.
- ↑ "Capítulo 4. Tipos, valores y variables" . Especificación del lenguaje Java® (Edición Java SE 8) . Oracle America, Inc. 2015. Consultado el 23 de febrero de 2015 .
- 1 2 3 "Asignación definida" . Especificación del lenguaje Java® (Java SE 8 Edition) . Oracle America, Inc. 2015. Consultado el 29 de octubre de 2016 .
- ↑ "JEP 500: Prepárate para que lo final signifique final" . openjdk.org . Consultado el 12 de enero de 2026 .
- ↑ ¿Cuál es el equivalente de `final` de Java en C#?
- Java (lenguaje de programación)