java - Diferencia entre final y efectivamente final.
text panel java (13)
Estoy jugando con lambdas en Java 8 y encontré advertencias de que local variables referenced from a lambda expression must be final or effectively final
. Sé que cuando uso variables dentro de la clase anónima, deben ser definitivas en la clase externa, pero aún así, ¿cuál es la diferencia entre final y efectivamente final ?
... a partir de Java SE 8, una clase local puede acceder a las variables y parámetros locales del bloque de cierre que son finales o efectivamente finales. Una variable o parámetro cuyo valor nunca se cambia después de su inicialización es efectivamente final.
Por ejemplo, supongamos que la variable numberLength
no se declara definitiva y agrega la instrucción de asignación marcada en el constructor PhoneNumber
:
public class OutterClass {
int numberLength; // <== not *final*
class PhoneNumber {
PhoneNumber(String phoneNumber) {
numberLength = 7; // <== assignment to numberLength
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
...
}
...
}
Debido a esta declaración de asignación, la variable numberLength ya no es realmente definitiva. Como resultado, el compilador Java genera un mensaje de error similar a "las variables locales a las que se hace referencia desde una clase interna deben ser finales o efectivamente finales" donde la clase interna PhoneNumber intenta acceder a la variable numberLength:
http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html
http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
Si pudiera agregar el modificador
final
a una variable local, efectivamente fue final.
Las expresiones Lambda pueden acceder
variables estáticas,
variables de instancia,
efectivamente los parámetros finales del método, y
Efectivamente las variables locales finales.
Adicionalmente,
Una variable
effectively final
es una variable cuyo valor nunca se cambia, pero no se declara con la palabra clavefinal
.
Fuente: Empezando con Java: de estructuras de control a través de objetos (6ª edición), Tony Gaddis
Además, no olvide el significado de final
que se inicializa exactamente una vez antes de que se use por primera vez.
Sin embargo, a partir de Java SE 8, una clase local puede acceder a las variables y parámetros locales del bloque adjunto que son finales o efectivamente finales.
Esto no comenzó en Java 8, lo uso desde hace mucho tiempo. Este código utilizado (antes de java 8) para ser legal:
String str = ""; //<-- not accesible from anonymous classes implementation final String strFin = ""; //<-- accesible button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String ann = str; // <---- error, must be final (IDE''s gives the hint); String ann = strFin; // <---- legal; String str = "legal statement on java 7," +"Java 8 doesn''t allow this, it thinks that I''m trying to use the str declared before the anonymous impl."; //we are forced to use another name than str } );
Cuando una expresión lambda usa una variable local asignada desde su espacio cerrado, hay una restricción importante. Una expresión lambda solo puede usar una variable local cuyo valor no cambie. Esa restricción se conoce como " captura de variable " que se describe como; Valores de captura de expresión lambda, no variables .
Las variables locales que puede utilizar una expresión lambda se conocen como " efectivamente final ".
Una variable final efectiva es aquella cuyo valor no cambia después de su primera asignación. No es necesario declarar explícitamente dicha variable como final, aunque hacerlo no sería un error.
Veamos con un ejemplo, tenemos una variable local i que se inicializa con el valor 7, con la expresión lambda que intentamos cambiar ese valor asignando un nuevo valor a i. Esto dará como resultado un error del compilador: " La variable local que he definido en un ámbito adjunto debe ser final o efectivamente final "
@FunctionalInterface
interface IFuncInt {
int func(int num1, int num2);
public String toString();
}
public class LambdaVarDemo {
public static void main(String[] args){
int i = 7;
IFuncInt funcInt = (num1, num2) -> {
i = num1 + num2;
return i;
};
}
}
De un artículo de ''Brian Goetz'',
''Efectivamente final'' es una variable que no daría un error de compilación si fuera agregada por ''final''
Declarar una variable final
o no declararla final
, pero mantenerla efectivamente final puede resultar (depende del compilador) en un bytecode diferente.
Echemos un vistazo a un pequeño ejemplo:
String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String ann = str; // <---- error, must be final (IDE''s gives the hint);
String ann = strFin; // <---- legal;
String str = "legal statement on java 7,"
+"Java 8 doesn''t allow this, it thinks that I''m trying to use the str declared before the anonymous impl.";
//we are forced to use another name than str
}
);
El bytecode correspondiente del método main
(Java 8u161 en Windows 64 Bit):
public static void main(java.lang.String[]); Code: 0: iconst_1 1: istore_1 2: iconst_1 3: istore_2 4: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 7: iconst_1 8: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V 11: iload_2 12: ifeq 22 15: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 18: iload_2 19: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V 22: iload_2 23: ifne 33 26: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 29: iload_2 30: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V 33: return
La tabla de números de línea correspondiente:
LineNumberTable: line 6: 0 line 7: 2 line 10: 4 line 15: 11 line 16: 15 line 18: 22 line 19: 26 line 21: 33
Como vemos, el código fuente en las líneas 12
, 13
, 14
no aparece en el código de bytes. Eso es porque i
es true
y no cambiaré su estado. Por lo tanto, este código es inalcanzable (más en esta respuesta ). Por la misma razón, el código en la línea 9
también se pierde. El estado de i
no tiene que ser evaluado ya que es true
.
Por otro lado, aunque la variable j
es efectivamente final , no se procesa de la misma manera. No hay tales optimizaciones aplicadas. El estado de j
se evalúa dos veces. El bytecode es el mismo, independientemente de que j
sea efectivamente final .
Encuentro que la forma más sencilla de explicar "efectivamente final" es imaginar agregar el modificador final
a una declaración de variable. Si, con este cambio, el programa continúa comportándose de la misma manera, tanto en tiempo de compilación como en tiempo de ejecución, entonces esa variable es efectivamente final.
Esta variable a continuación es definitiva , por lo que no podemos cambiar su valor una vez que se haya inicializado. Si intentamos conseguiremos un error de compilación ...
final int variable = 123;
Pero si creamos una variable como esta, podemos cambiar su valor ...
int variable = 123;
variable = 456;
Pero en Java 8 , todas las variables son finales por defecto. Pero la existencia de la segunda línea en el código lo hace no definitivo . Entonces, si eliminamos la segunda línea del código anterior, nuestra variable ahora es "efectivamente final" ...
int variable = 123;
Entonces ... Cualquier variable que se asigne una vez y solo una vez, es "efectivamente final" .
Según los docs :
Una variable o parámetro cuyo valor nunca se cambia después de su inicialización es efectivamente final.
Básicamente, si el compilador encuentra una variable que no aparece en las asignaciones fuera de su inicialización, entonces la variable se considera efectivamente final .
Por ejemplo, considere alguna clase:
public class Foo {
public void baz(int bar) {
// While the next line is commented, bar is effectively final
// and while it is uncommented, the assignment means it is not
// effectively final.
// bar = 2;
}
}
Una variable es final o efectivamente final cuando se inicializa una vez y nunca se muta en su clase de propietario. Y no podemos inicializarlo en bucles o clases internas .
Final :
final int number;
number = 23;
Efectivamente final :
int number;
number = 34;
El tema final efectivo se describe en JLS 4.12.4 y el último párrafo consiste en una explicación clara:
Si una variable es efectivamente final, agregar el modificador final a su declaración no introducirá ningún error en tiempo de compilación. A la inversa, una variable o parámetro local que se declara final en un programa válido se convierte efectivamente en final si se elimina el modificador final.
final es una variable declarar con palabra clave final
, ejemplo:
final double pi = 3.14 ;
Sigue siendo final
en todo el programa.
efectivamente final : cualquier variable local o parámetro al que se le asigna un valor solo una vez en este momento (o actualizado solo una vez). Puede que no permanezca efectivamente final a través del programa. por lo tanto, esto significa que la variable final efectiva puede perder su propiedad final efectiva inmediatamente después del momento en que se asigna / actualiza al menos una asignación más. ejemplo:
class EffectivelyFinal {
public static void main(String[] args) {
calculate(124,53);
}
public static void calculate( int operand1, int operand2){
int rem = 0; // operand1, operand2 and rem are effectively final here
rem = operand1%2 // rem lost its effectively final property here because it gets its second assignment
// operand1, operand2 are still effectively final here
class operators{
void setNum(){
operand1 = operand2%2; // operand1 lost its effectively final property here because it gets its second assignment
}
int add(){
return rem + operand2; // does not compile because rem is not effectively final
}
int multiply(){
return rem * operand1; // does not compile because both rem and operand1 are not effectively final
}
}
}
}
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
// The following statement causes the compiler to generate
// the error "local variables referenced from a lambda expression
// must be final or effectively final" in statement A:
//
// x = 99;
}
}
}
Como han dicho otros, una variable o parámetro cuyo valor nunca se cambia después de su inicialización es efectivamente final. En el código anterior, si cambia el valor de x
en la clase interna FirstLevel
, el compilador le mostrará el mensaje de error:
Las variables locales a las que se hace referencia desde una expresión lambda deben ser finales o efectivamente finales.