Diferencia entre final y efectivamente final

351

Estoy jugando con lambdas en Java 8 y me encontré con una advertencia local variables referenced from a lambda expression must be final or effectively final. Sé que cuando uso variables dentro de una clase anónima deben ser finales en la clase externa, pero aún así, ¿cuál es la diferencia entre final y efectivamente final ?

alex
fuente
2
Muchas respuestas, pero todas equivalen esencialmente a "ninguna diferencia". ¿Pero es eso realmente cierto? Desafortunadamente, parece que no puedo encontrar una especificación de lenguaje para Java 8.
Aleksandr Dubinsky
3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis
@AleksandrDubinsky no es "realmente" cierto. Encontré una excepción a esta regla. Una variable local inicializada con una constante no es una expresión constante para el compilador. No puede usar dicha variable para un caso en un interruptor / caso hasta que agregue explícitamente la palabra clave final. Por ejemplo, "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

Respuestas:

234

... comenzando en 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 cambia después de que se inicializa es efectivamente final.

Por ejemplo, suponga que la variable numberLengthno se declara final y agrega la instrucción de asignación marcada en el PhoneNumberconstructor:

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 efectivamente final. Como resultado, el compilador de 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

Suresh Atta
fuente
68
+1 Nota: si no se cambia una referencia, es efectivamente final incluso si se cambia el objeto al que se hace referencia.
Peter Lawrey
1
@stanleyerror Esto podría ser de alguna ayuda: stackoverflow.com/questions/4732544/…
1
Creo que más útil que un ejemplo de no efectivamente final, es un ejemplo de cuando algo es efectivamente final. Aunque la descripción lo deja claro. Var no necesita ser declarado final si ningún código cambia su valor.
Skychan
1
El ejemplo es incorrecto. Este código se compila perfectamente (sin puntos, por supuesto). Para obtener el error del compilador, este código debe estar dentro de algún método para que se numberLengthconvierta en una variable local de este método.
mykola
1
¿Hay alguna razón por la cual este ejemplo es tan complicado? ¿Por qué la mayoría del código trata con una operación de expresiones regulares completamente irrelevante? Y, como ya dijo @mykola, le falta por completo la marca con respecto a la propiedad final efectiva , ya que eso solo es relevante para las variables locales y no hay una variable local en este ejemplo.
Holger
131

Creo que la forma más simple de explicar "efectivamente final" es imaginar agregar el finalmodificador 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.

Maurice Naftalin
fuente
44
Esto es cierto, siempre y cuando se comprenda bien la "final" de Java 8. De lo contrario, miraría una variable no declarada final a la que hiciste una asignación más tarde y erróneamente pensaría que no era final. Puede decir "por supuesto" ... pero no todos prestan tanta atención a los últimos cambios de versión de idioma como deberían.
fool4jesus
8
Una excepción a esta regla es que una variable local inicializada con una constante no es una expresión constante para el compilador. No puede usar dicha variable para un caso en un interruptor / caso hasta que agregue explícitamente la palabra clave final. Por ejemplo, "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen
2
@HennoVermeulen switch-case no es una excepción a la regla en esta respuesta. El lenguaje especifica que case krequiere una expresión constante que podría ser una variable constante ("Una variable constante es una variable final de tipo primitivo o tipo String que se inicializa con una expresión constante" JLS 4.12.4 ), que es un caso especial de una variable final variable.
Colin D Bennett
3
En mi ejemplo, el compilador se queja de que k no es una expresión constante, por lo que no se puede usar para el cambio. Cuando se agrega el final, el comportamiento de compilación cambia porque ahora es una variable constante y se puede usar en el conmutador. Entonces tienes razón: la regla sigue siendo correcta. Simplemente no se aplica a este ejemplo y no dice si k es efectivamente final o no.
Henno Vermeulen
36

Según los documentos :

Una variable o parámetro cuyo valor nunca cambia después de que se inicializa es efectivamente final.

Básicamente, si el compilador encuentra que una variable no aparece en 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;
    }
}
Mark Elliot
fuente
Los documentos hablan de variables locales. baren su ejemplo no es una variable local, sino un campo. "Efectivamente final" en el mensaje de error anterior no se aplica a los campos en absoluto.
Antti Haapala
66
@AnttiHaapala bares un parámetro aquí, no un campo.
peter.petrov
30

'Efectivamente final' es una variable que no daría un error del compilador si fuera agregada por 'final'

De un artículo de 'Brian Goetz',

Informalmente, una variable local es efectivamente final si su valor inicial nunca se cambia; en otras palabras, declararla como definitiva no causaría una falla de compilación.

lambda-state-final- Brian Goetz

Ajeet Ganga
fuente
2
esta respuesta se muestra como una cita, sin embargo, no hay un texto exacto en el artículo de Brian, por supuesto, no la palabra adjunta . En cambio, se trata de una cita: informalmente, una variable local es efectivamente final si su valor inicial nunca se cambia; en otras palabras, declararla como definitiva no provocaría un error de compilación.
lcfd
Del artículo, copia literal: informalmente, una variable local es efectivamente final si su valor inicial nunca se cambia; en otras palabras, declararla final no causaría una falla de compilación.
Ajeet Ganga
26

Esta variable a continuación es final , por lo que no podemos cambiar su valor una vez inicializada. Si lo intentamos, obtendremos 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 final . 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" .

Eurig Jones
fuente
Tan simple como debería ser la respuesta.
superigno
@Eurig, cita requerida para "todas las variables son finales por defecto".
Pacerier
10

Una variable es final o efectivamente final cuando se inicializa una vez y nunca 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;

Nota : Final y Final efectivo son similares (su valor no cambia después de la asignación) pero solo que las variables finales efectivas no se declaran con la palabra clave final.

samadadi
fuente
7

Cuando una expresión lambda usa una variable local asignada de su espacio cerrado, existe 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 variable " que se describe como; valores de captura de expresiones lambda, no variables .
Las variables locales que puede usar una expresión lambda se conocen como " efectivamente final ".
Una variable final efectiva es aquella cuyo valor no cambia después de ser asignada por primera vez. No es necesario declarar explícitamente una 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 estamos tratando de cambiar ese valor asignando un nuevo valor a i. Esto generará un error del compilador: " La variable local que definí en un ámbito de inclusión 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;
        };
    }   
}
infoj
fuente
2

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. Por el contrario, una variable o parámetro local que se declara final en un programa válido se vuelve efectivamente final si se elimina el modificador final.

Novdar
fuente
2

final es una variable declarar con palabra clave final, ejemplo:

final double pi = 3.14 ;

permanece finaldurante todo el programa.

efectivamente final : cualquier variable o parámetro local al que se le asigna un valor solo una vez en este momento (o se actualiza solo una vez). Puede que no permanezca efectivamente final durante todo el programa. Esto significa que la variable final efectiva podría perder su propiedad final efectiva inmediatamente después del tiempo que se le 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
            }
        }   
   }    
}
El método científico
fuente
Esto es incorrecto de acuerdo con la Especificación del lenguaje Java: " Cada vez que ocurre como el lado izquierdo en una expresión de asignación, definitivamente no está asignado y no está asignado definitivamente antes de la asignación". Una variable / parámetro es siempre o nunca efectivamente final. Más explícitamente, si no puede agregar la finalpalabra clave a una declaración sin introducir errores de compilación, entonces no es efectivamente final . Es el contrapositivo de esta declaración: "Si una variable es efectivamente final, agregar el modificador final a su declaración no introducirá ningún error en tiempo de compilación".
AndrewF
Los comentarios en el código de ejemplo son incorrectos por todos los motivos descritos en mi comentario. "Efectivamente final" no es un estado que pueda cambiar con el tiempo.
AndrewF
@AndrewF si no cambia con el tiempo, ¿qué crees que no compila la última línea? rem fue efectivamente final en la línea 1 en el método de cálculo. Sin embargo, en la última línea, el compilador se queja de que rem no es efectivamente final
El Método Científico
Tiene razón en que parte del código debe eliminarse del bloque de código para compilar, pero eso no refleja el comportamiento en tiempo de ejecución. En el momento de la compilación, puede decidir si una variable es efectivamente final o no; según la especificación, siempre es efectivamente final o nunca es efectivamente final. El compilador puede saber mirando estáticamente cómo se usa la variable en todo su alcance. La propiedad no puede ganarse ni perderse mientras se ejecuta el programa. El término está bien definido por la especificación: consulte las otras respuestas, que lo explican bastante bien.
AndrewF
1
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 cambia después de que se inicializa es efectivamente final. En el código anterior, si cambia el valor de xen la clase interna, FirstLevelel compilador le dará el mensaje de error:

Las variables locales a las que se hace referencia desde una expresión lambda deben ser finales o efectivamente finales.

Girish
fuente
1

Si pudiera agregar el finalmodificador a una variable local, era efectivamente final.

Las expresiones lambda pueden acceder

  • variables estáticas

  • variables de instancia,

  • efectivamente los parámetros finales del método, y

  • efectivamente variables locales finales.

Fuente: OCP: Guía de estudio de Oracle Certified Professional Java SE 8 Programmer II, Jeanne Boyarsky, Scott Selikoff

Adicionalmente,

Una effectively finalvariable es una variable cuyo valor nunca cambia, pero no se declara con la finalpalabra clave.

Fuente: Comenzando con Java: desde estructuras de control a través de objetos (6a edición), Tony Gaddis

Además, no olvide el significado de finalque se inicializa exactamente una vez antes de que se use por primera vez.

snr
fuente
0

Declarar una variable finalo no declararla final, pero mantenerla efectivamente final puede resultar (depende del compilador) en un código de bytes diferente.

Echemos un vistazo a un pequeño ejemplo:

    public static void main(String[] args) {
        final boolean i = true;   // 6  // final by declaration
        boolean j = true;         // 7  // effectively final

        if (i) {                  // 9
            System.out.println(i);// 10
        }
        if (!i) {                 // 12
            System.out.println(i);// 13
        }
        if (j) {                  // 15
            System.out.println(j);// 16
        }
        if (!j) {                 // 18
            System.out.println(j);// 19
        }
    }

El maincódigo de bytes correspondiente del método (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 líneas 12, 13, 14no aparece en el código de bytes. Eso es porque ies truey 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 9también falla. El estado de ino tiene que ser evaluado, ya que es trueseguro.

Por otro lado, aunque la variable jes efectivamente final , no se procesa de la misma manera. No hay tales optimizaciones aplicadas. El estado de jse evalúa dos veces. El código de bytes es el mismo independientemente de que jsea efectivamente final .

LuCio
fuente
Consideraría esto una ineficiencia del compilador, y no necesariamente una que todavía sería cierta en los compiladores más nuevos. En una compilación perfecta, si una variable es efectivamente final, generará exactamente las mismas optimizaciones que una declarada final. Así que no confíe en la noción de que efectivamente final es automáticamente más lento que declarar algo final.
AndrewF
@AndrewF Generalmente tienes razón, el comportamiento puede cambiar. Es por eso que escribí " puede resultar (depende del compilador) en diferentes códigos de bytes ". Solo debido a la optimización que falta (código de bytes diferente) no asumiría que la ejecución sea más lenta. Pero sigue siendo una diferencia en el caso que se muestra.
LuCio
0

La variable final efectiva es una variable local que es:

  1. No definido como final
  2. Asignado a SOLO una vez.

Mientras que una variable final es una variable que es:

  1. declarado con una finalpalabra clave.
Jimmy_Rw
fuente
-6

Sin embargo, comenzando en Java SE 8, una clase local puede acceder a variables locales y parámetros del bloque que los encierra que son finales o efectivamente finales.

Esto no comenzó en Java 8, lo uso desde hace mucho tiempo. Este código solía (antes de Java 8) 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
    }
);
FiruzzZ
fuente
2
La declaración se refiere al hecho de que en <Java 8, solo final se puede acceder a las variables, pero en Java 8 también a aquellas que son efectivamente finales.
Antti Haapala
Solo veo código que no funciona, independientemente de si está utilizando Java 7 o Java 8.
Holger