final (Java) - final (Java)
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 cambiarse mediante operaciones en el objeto, pero la variable siempre se referirá al mismo objeto (esta propiedad de final se llama no transitividad ). Esto también se aplica a las matrices, porque las matrices son objetos; si una final variable tiene 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.
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:
public final class MyFinalClass {...}
public class ThisIsWrong extends MyFinalClass {...} // forbidden
Métodos finales
Un método final no puede ser anulado u oculto por subclases. Esto se utiliza para evitar que un comportamiento inesperado de una subclase altere un método que puede ser crucial para la función o la coherencia de la clase.
Ejemplo:
public class Base
{
public void m1() {...}
public final void m2() {...}
public static void m3() {...}
public static final void m4() {...}
}
public class Derived extends Base
{
public void m1() {...} // OK, overriding Base#m1()
public void m2() {...} // forbidden
public static void m3() {...} // OK, hiding Base#m3()
public static void m4() {...} // forbidden
}
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.
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. (Nota: si la variable es una referencia, esto significa que la variable no se puede volver a vincular 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.
Ejemplo:
public class Sphere {
// pi is a universal constant, about as constant as anything can be.
public static final double PI = 3.141592653589793;
public final double radius;
public final double xPos;
public final double yPos;
public final double zPos;
Sphere(double x, double y, double z, double r) {
radius = 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:
public final Position pos;
donde pos es un objeto con tres propiedades pos.x , pos.y y pos.z . Entonces pos no se puede asignar, pero las tres propiedades sí, a menos que sean definitivas.
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 que devuelva 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) {
// do something with obj
}
Dado que la variable obj sale del alcance con cada iteración del ciclo, en realidad se vuelve a declarar en cada iteración, lo que permite obj que se utilice el mismo token (es decir ) para representar múltiples variables.
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.
import java.awt.Point;
public class FinalDemo {
static class CoordinateSystem {
private final Point origin = new Point(0, 0);
public Point getOrigin() { return origin; }
}
public static void main(String[] args) {
CoordinateSystem coordinateSystem = new CoordinateSystem();
coordinateSystem.getOrigin().x = 15;
assert coordinateSystem.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.
import javax.swing.*;
public class FooGUI {
public static void main(String[] args) {
//initialize GUI components
final JFrame jf = new JFrame("Hello world!"); //allows jf to be accessed from inner class body
jf.add(new JButton("Click me"));
// pack and make visible on the Event-Dispatch Thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jf.pack(); //this would be a compile-time error if jf were not 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. 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.
final boolean hasTwoDigits;
if (number >= 10 && number < 100) {
hasTwoDigits = true;
}
if (number > -100 && number <= -10) {
hasTwoDigits = true; // compile-error because the final variable might already be assigned.
}
Además, también se debe asignar definitivamente una final en blanco antes de acceder.
final boolean isEven;
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // compile-error because the variable was not assigned in the else-case.
Sin embargo, tenga en cuenta que una variable local no final también debe asignarse definitivamente antes de acceder.
boolean isEven; // *not* final
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // Same compile-error because the non-final variable was not assigned in the else-case.
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, desechar la constness 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
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).