En el lenguaje de programación Java , la final
palabra clave se usa en varios contextos para definir una entidad que solo se puede asignar una vez.
Una vez final
que se ha asignado una variable, siempre contiene el mismo valor. Si una final
variable tiene una referencia a un objeto, entonces el estado del objeto puede ser cambiado por operaciones en el objeto, pero la variable siempre se referirá al mismo objeto (esta propiedad de final
se llama no transitividad [1] ). Esto se aplica también a las matrices, porque las matrices son objetos; si una final
variable contiene una referencia a una matriz, entonces los componentes de la matriz pueden cambiarse mediante operaciones en la matriz, pero la variable siempre se referirá a la misma matriz. [2]
Clases finales
Una clase final no se puede subclasificar. Como hacer esto puede conferir beneficios de seguridad y eficiencia, muchas de las clases de biblioteca estándar de Java son finales, como java.lang.System
y java.lang.String
.
Ejemplo:
clase final pública MyFinalClass {...} public class ThisIsWrong extiende MyFinalClass {...} // prohibido
Métodos finales
Un método final no puede ser anulado u oculto por subclases. [3] Esto se usa para evitar que un comportamiento inesperado de una subclase altere un método que puede ser crucial para la función o consistencia de la clase. [4]
Ejemplo:
public class Base { public void m1 () {...} public final void m2 () {...} public static void m3 () {...} public static final void m4 () {...} }public class Derived extiende Base { public void m1 () {...} // OK, anulando Base # m1 () public void m2 () {...} // prohibido public static void m3 () {...} // OK, ocultando Base # m3 () public static void m4 () {...} // prohibido }
Un error común es que declarar un método como final
mejora la eficiencia al permitir que el compilador inserte directamente el método donde sea que se llame (ver expansión en línea ). Debido a 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 incorporar, si el método es final o no. [5]
Los compiladores de código de máquina que generan código de máquina específico de plataforma directamente ejecutable son una excepción. Cuando se utiliza un enlace estático , el compilador puede asumir con seguridad que los métodos y las variables calculables en tiempo de compilación pueden estar en línea.
Variables finales
Una variable final solo se puede inicializar una vez, ya sea mediante un inicializador o una instrucción de asignación. No es necesario inicializarla en el punto de declaración: esto se denomina variable "final en blanco". Una variable de instancia final en blanco de una clase debe asignarse definitivamente en cada constructor de la clase en la que se declara; de manera similar, una variable estática final en blanco debe asignarse definitivamente en un inicializador estático de la clase en la que se declara; de lo contrario, se produce un error en tiempo de compilación en ambos casos. [6] (Nota: si la variable es una referencia, esto significa que la variable no puede volver a vincularse para hacer referencia a otro objeto. Pero el objeto al que hace referencia sigue siendo mutable , si originalmente era mutable).
A diferencia del valor de una constante , el valor de una variable final no se conoce necesariamente en el momento de la compilación. Se considera una buena práctica representar las constantes finales en mayúsculas, utilizando guiones bajos para separar palabras. [7]
Ejemplo:
Esfera de clase pública { // pi es una constante universal, casi tan constante como puede ser cualquier cosa. PI doble final estática pública = 3,141592653589793 ; doble radio final público ; públicos finales dobles xPos ; público final doble yPos ; públicos finales dobles 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 zPos
dará lugar a un error de compilación. De hecho, incluso si el constructor no establece una variable final, intentar establecerla fuera del constructor resultará en un error de compilación.
Para ilustrar que la finalidad no garantiza la inmutabilidad: supongamos que reemplazamos las tres variables de posición con una sola:
posición final pública pos ;
donde pos
es un objeto con tres propiedades pos.x
, pos.y
y pos.z
. Entonces pos
no se puede asignar a, pero las tres propiedades sí, a menos que sean definitivas en sí mismas.
Al igual que la inmutabilidad total , el uso de variables finales tiene grandes ventajas, especialmente en la optimización. Por ejemplo, Sphere
probablemente tendrá una función devolviendo su volumen; saber que su radio es constante nos permite memorizar el volumen calculado. Si tenemos relativamente pocos Sphere
sy necesitamos sus volúmenes con mucha frecuencia, la ganancia de rendimiento podría ser sustancial. Hacer el radio de a Sphere
final
informa a los desarrolladores y compiladores que este tipo de optimización es posible en todo el código que usa Sphere
s.
Aunque parece violar el final
principio, la siguiente es una declaración legal:
for ( final SomeObject obj : someList ) { // hacer algo con obj }
Dado que la variable obj sale del alcance con cada iteración del bucle, en realidad se vuelve a declarar en cada iteración, lo que permite obj
que se use el mismo token (es decir ) para representar múltiples variables. [8]
Variables finales en objetos anidados
Las variables finales se pueden utilizar para construir árboles de objetos inmutables. Una vez construidos, se garantiza que estos objetos no cambiarán más. Para lograr esto, una clase inmutable solo debe tener campos finales, y estos campos finales solo pueden tener tipos inmutables ellos mismos. 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, la expectativa no es que cualquier cosa 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 en (0, 0). El origen se implementa usando un java.awt.Point
aunque, y esta clase define sus campos como públicos y modificables. Esto significa que incluso cuando se llega al origin
objeto a través de una ruta de acceso con solo las variables finales, ese objeto aún se puede modificar, como lo demuestra el código de ejemplo a continuación.
importar java.awt.Point ;public class FinalDemo { CoordinateSystem clase estática { origen del punto final privado = nuevo punto ( 0 , 0 ); punto público getOrigin () { origen de retorno ; } } public static void main ( String [] args ) { CoordinateSystem CoordinateSystem = new CoordinateSystem (); CoordinarSystem . getOrigin (). x = 15 ; afirmar sistema de coordenadas . getOrigin (). getX () == 0 ; } }
La razón de esto es que declarar una variable final solo significa que esta variable apuntará al mismo objeto en cualquier momento. Sin embargo, el objeto al que apunta la variable no está influenciado por esa variable final. En el ejemplo anterior, las coordenadas xey del origen se pueden modificar libremente.
Para evitar esta situación indeseable, un requisito común es que todos los campos de un objeto inmutable deben ser finales, y que los tipos de estos campos deben ser inmutables en sí mismos. Esto descalifica a java.util.Date
y java.awt.Point
varias otras clases de ser utilizadas en tales objetos inmutables.
Clases finales e internas
Cuando se define una clase interna anónima dentro del cuerpo de un método, todas las variables declaradas final
en el alcance de ese método son accesibles desde dentro de la clase interna. Para valores escalares, una vez asignado, el valor de la final
variable no puede cambiar. Para los valores de objeto, la referencia no puede cambiar. Esto permite que el compilador de Java "capture" el valor de la variable en tiempo de ejecución y almacene una copia como un campo en la clase interna. Una vez que el método externo ha terminado y su marco de pila se ha eliminado, la variable original desaparece pero la copia privada de la clase interna persiste en la propia memoria de la clase.
importar javax.swing. * ; clase pública FooGUI { public static void main ( String [] args ) { // inicializar componentes GUI final JFrame jf = new JFrame ( "¡Hola mundo!" ); // permite acceder a jf desde el cuerpo interno de la clase jf . add ( new JButton ( "Haga clic en mí" )); // empaquetar y hacer visible en Event-Dispatch Thread SwingUtilities . invokeLater ( new Runnable () { @Override public void run () { jf . pack (); // esto sería un error en tiempo de compilación si jf no fuera final jf . setLocationRelativeTo ( null ); jf . setVisible ( true ); } }); } }
Final en blanco
El final en blanco , que se introdujo en Java 1.1, es una variable final cuya declaración carece de inicializador. [9] [10] Antes de Java 1.1, se requería una variable final para tener un inicializador. Una final en blanco, por definición de "final", solo se puede asignar una vez. es decir, debe desasignarse cuando se produce una asignación. Para hacer esto, un compilador de Java ejecuta un análisis de flujo para asegurarse de que, para cada asignación a una variable final en blanco, la variable definitivamente no esté asignada antes de la asignación; de lo contrario, se producirá un error en tiempo de compilación. [11]
hasTwoDigits booleano final ; if ( número > = 10 && número < 100 ) { hasTwoDigits = true ; } if ( número > - 100 && número <= - 10 ) { hasTwoDigits = true ; // error de compilación porque es posible que la variable final ya esté asignada. }
Además, también se debe asignar definitivamente una final en blanco antes de acceder. [11]
isEven booleano final ;if ( número % 2 == 0 ) { isEven = true ; }Sistema . fuera . println ( esEven ); // error de compilación porque la variable no fue asignada en el caso else.
Sin embargo, tenga en cuenta que una variable local no final también debe asignarse definitivamente antes de acceder. [11]
boolean isEven ; // * no * finalif ( número % 2 == 0 ) { isEven = true ; }Sistema . fuera . println ( esEven ); // Mismo error de compilación porque la variable no final no fue asignada en el caso else.
Analógico C / C ++ de variables finales
En C y C ++ , la construcción análoga es la const
palabra clave . Esto difiere sustancialmente de final
Java, básicamente en ser un calificador de tipo : const
es parte del tipo , no solo parte del identificador (variable). Esto también significa que la constancia de un valor se puede cambiar mediante conversión (conversión de tipo explícita), en este caso conocida como "conversión constante". No obstante, deshacerse de la constancia y luego modificar el objeto da como resultado un comportamiento indefinido si el objeto se declaró originalmente const
. Java final
es una regla estricta, por lo que es imposible compilar código que rompa o salte directamente las restricciones finales. Sin embargo, utilizando la reflexión , a menudo es posible modificar las variables finales. Esta característica se utiliza 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 apuntados por el puntero son constantes. Aplicar const
a un puntero en sí, como en SomeClass * const ptr
, significa que el contenido al que se hace referencia se puede modificar, pero la referencia en sí no puede (sin conversión). Este uso da como resultado un comportamiento que imita el comportamiento de una final
referencia de variable en Java. Por el contrario, cuando se aplica const solo a los datos referenciados, como en const SomeClass * ptr
, el contenido no se puede modificar (sin conversión), pero la referencia en sí sí. Tanto la referencia como el contenido al que se hace referencia se pueden declarar como const
.
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 código de bytes, C # tiene MSIL; Java no tiene soporte para punteros (memoria real), C # es lo mismo.
En cuanto a la palabra clave final, 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
[12].
Tenga en cuenta que una diferencia clave entre la palabra clave derivada de C / C ++ const
y la palabra clave C # readonly
es que const
se evalúa en tiempo de compilación, mientras que readonly
se 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).
Referencias
- ^ Coblenz, Michael; Sol, Joshua; Aldrich, Jonathan; Myers, Brad; Weber, Sam; Shull, Forrest (14 a 22 de mayo de 2016). "Explorando el soporte del lenguaje para la inmutabilidad". La 38ª Conferencia Internacional de Ingeniería de Software .
- ^ Especificación del lenguaje Java # 4.12.4
- ^ JLS 8.4.3.3. métodos finales
- ^ Escritura de clases y métodos finales
- ^ Teoría y práctica de Java: ¿Es esa su respuesta final?
- ^ Especificación del lenguaje Java # 8.3.1.2.
- ^ http://geosoft.no/development/javastyle.html
- ^ Pattis, Richard E. "Más Java" . Programación avanzada / Práctica 15-200 . Facultad de Ciencias de la Computación de la 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" . La especificación del lenguaje Java® (Java SE 8 Edition) . Oracle America, Inc. 2015 . Consultado el 23 de febrero de 2015 .
- ^ a b c "Asignación Definida" . La especificación del lenguaje Java® (Java SE 8 Edition) . Oracle America, Inc. 2015 . Consultado el 29 de octubre de 2016 .
- ^ ¿Cuál es el equivalente al final de Java en C #?