Diferença entre final e efetivamente final


350

Estou brincando com lambdas no Java 8 e me deparei com um aviso local variables referenced from a lambda expression must be final or effectively final. Eu sei que quando eu uso variáveis ​​dentro da classe anônima, elas devem ser finais na classe externa, mas ainda assim - qual é a diferença entre final e efetivamente final ?


2
Muitas respostas, mas todas são essencialmente "sem diferença". Mas isso é realmente verdadeiro? Infelizmente, eu não consigo encontrar uma especificação de linguagem para Java 8.
Aleksandr Dubinsky

3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis

@AleksandrDubinsky não é "realmente" verdadeiro. Eu encontrei uma exceção a esta regra. Uma variável local inicializada com uma constante não é uma expressão constante para o compilador. Você não pode usar essa variável para um caso em uma opção / caso até adicionar explicitamente a palavra-chave final. Por exemplo, "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

Respostas:


233

... a partir do Java SE 8, uma classe local pode acessar variáveis ​​e parâmetros locais do bloco anexo que são finais ou efetivamente finais. Uma variável ou parâmetro cujo valor nunca é alterado após a inicialização é efetivamente final.

Por exemplo, suponha que a variável numberLengthnão seja declarada final e você adicione a instrução de atribuição marcada no PhoneNumberconstrutor:

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;
     }

  ...

  }

...

}

Por causa dessa declaração de atribuição, a variável numberLength não é mais definitiva. Como resultado, o compilador Java gera uma mensagem de erro semelhante a "variáveis ​​locais referenciadas de uma classe interna devem ser finais ou efetivamente finais" onde a classe interna PhoneNumber tenta acessar a variável numberLength:

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html


68
+1 Nota: se uma referência não for alterada, ela é efetivamente final, mesmo que o objeto referenciado seja alterado.
precisa saber é o seguinte

11
@stanleyerror Isso pode ser de alguma ajuda: stackoverflow.com/questions/4732544/…

11
Eu acho que mais útil do que um exemplo de não efetivamente final, é um exemplo de quando algo é efetivamente final. Embora a descrição deixe claro. Var não precisa ser declarado final se nenhum código alterar seu valor.
Skychan #

11
O exemplo está incorreto. Este código compila perfeitamente (sem pontos, é claro). Para obter o erro do compilador, esse código deve estar dentro de algum método para que numberLengthse torne uma variável local desse método.
Mykola 06/07/19

11
Existe uma razão pela qual este exemplo é tão complicado? Por que a maioria do código está lidando com uma operação regex totalmente irrelevante? E, como o @mykola já disse, está faltando completamente a marca referente à propriedade final efetiva , pois isso é relevante apenas para variáveis ​​locais e não há variável local neste exemplo.
Holger

131

Acho que a maneira mais simples de explicar "efetivamente final" é imaginar adicionar o finalmodificador a uma declaração de variável. Se, com essa alteração, o programa continuar a se comportar da mesma maneira, tanto em tempo de compilação quanto em tempo de execução, essa variável será efetivamente final.


4
Isso é verdade, desde que o entendimento do "final" do java 8 seja bem entendido. Caso contrário, eu examinaria uma variável não declarada final para a qual você fez uma atribuição mais tarde e pensaria erroneamente que não era final. Você pode dizer "é claro" ... mas nem todos prestam tanta atenção às alterações da versão mais recente do idioma quanto deveriam.
fool4jesus

8
Uma exceção a essa regra é que uma variável local inicializada com uma constante não é uma expressão constante para o compilador. Você não pode usar essa variável para um caso em uma opção / caso até adicionar explicitamente a palavra-chave final. Por exemplo, "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

2
A caixa de mudança @HennoVermeulen não é uma exceção à regra nesta resposta. A linguagem especifica que case krequer uma expressão constante que pode ser uma variável constante ("Uma variável constante é uma variável final do tipo ou tipo primitivo String que é inicializada com uma expressão constante" JLS 4.12.4 ), que é um caso especial de uma final variável.
Colin D Bennett

3
No meu exemplo, o compilador reclama que k não é uma expressão constante e, portanto, não pode ser usado para o comutador. Ao adicionar final, o comportamento da compilação muda porque agora é uma variável constante e pode ser usada no comutador. Então você está certo: a regra ainda está correta. Simplesmente não se aplica a este exemplo e não diz se k é efetivamente final ou não.
Henno Vermeulen

36

De acordo com os documentos :

Uma variável ou parâmetro cujo valor nunca é alterado após a inicialização é efetivamente final.

Basicamente, se o compilador descobrir que uma variável não aparece nas atribuições fora de sua inicialização, a variável será considerada efetivamente final .

Por exemplo, considere alguma classe:

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;
    }
}

Os documentos falam sobre variáveis ​​locais. barno seu exemplo não é uma variável local, mas um campo. "Efetivamente final" na mensagem de erro como acima não se aplica aos campos.
Antti Haapala 01/01

6
@AnttiHaapala baré um parâmetro aqui, não um campo.
Peter.petrov

30

'Efetivamente final' é uma variável que não daria erro ao compilador se fosse anexado por 'final'

De um artigo de 'Brian Goetz',

Informalmente, uma variável local é efetivamente final se seu valor inicial nunca for alterado - em outras palavras, declarar final não causaria uma falha de compilação.

final de estado de lambda- Brian Goetz


2
esta resposta é mostrada como uma citação, no entanto, não existe um texto exato no artigo de Brian, com certeza não a palavra anexada . Em vez disso, é uma citação: informalmente, uma variável local é efetivamente final se seu valor inicial nunca for alterado - em outras palavras, declarar final não causaria uma falha de compilação.
Lcfd 11/04/19

Da cópia literal do artigo: Informalmente, uma variável local é efetivamente final se seu valor inicial nunca for alterado - em outras palavras, declarar final não causaria falha na compilação.
Ajeet Ganga

26

Essa variável abaixo é final , portanto, não podemos alterar seu valor uma vez inicializado. Se tentarmos, obteremos um erro de compilação ...

final int variable = 123;

Mas se criarmos uma variável como essa, podemos mudar seu valor ...

int variable = 123;
variable = 456;

Mas no Java 8 , todas as variáveis ​​são finais por padrão. Mas a existência da 2ª linha no código a torna não final . Portanto, se removermos a segunda linha do código acima, nossa variável agora é "efetivamente final" ...

int variable = 123;

Portanto, qualquer variável atribuída uma e apenas uma vez é "efetivamente final" .


Tão simples como a resposta deve ser.
Superigno

@Eurig, citação necessária para "todas as variáveis ​​são finais por padrão".
Pacerier

10

Uma variável é final ou efetivamente final quando é inicializada uma vez e nunca é alterada em sua classe de proprietário. E não podemos inicializá- lo em loops ou classes internas .

Final :

final int number;
number = 23;

Efetivamente Final :

int number;
number = 34;

Nota : Final e Final efetivo são semelhantes (seu valor não muda após a atribuição), mas apenas as variáveis ​​finais efetivas não são declaradas com Palavra-chave final.


7

Quando uma expressão lambda usa uma variável local atribuída de seu espaço em anexo, há uma restrição importante. Uma expressão lambda pode usar apenas variável local cujo valor não muda. Essa restrição é referida como " captura variável ", que é descrita como; valores de captura de expressão lambda, não variáveis .
As variáveis ​​locais que uma expressão lambda pode usar são conhecidas como " efetivamente finais ". Vamos ver com um exemplo: temos uma variável local i que é inicializada com o valor 7; na expressão lambda, estamos tentando alterar esse valor atribuindo um novo valor a i. Isso resultará em erro do compilador - "
Uma variável efetivamente final é aquela cujo valor não muda após a primeira atribuição. Não há necessidade de declarar explicitamente uma variável como final, embora isso não seja um erro.

@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;
        };
    }   
}

2

O tópico final efetivo é descrito no JLS 4.12.4 e o último parágrafo consiste em uma explicação clara:

Se uma variável for efetivamente final, adicionar o modificador final à sua declaração não introduzirá nenhum erro em tempo de compilação. Por outro lado, uma variável local ou parâmetro declarado final em um programa válido se torna efetivamente final se o modificador final for removido.


2

final é uma variável declarada com palavra-chave final, exemplo:

final double pi = 3.14 ;

permanece finaldurante todo o programa.

efetivamente final : qualquer variável local ou parâmetro ao qual seja atribuído um valor apenas uma vez no momento (ou atualizado apenas uma vez). Pode não permanecer efetivamente final durante todo o programa. portanto, isso significa que a variável efetivamente final pode perder sua propriedade efetivamente final após o tempo em que é atribuída / atualizada pelo menos mais uma atribuição. exemplo:

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
            }
        }   
   }    
}

Isso está incorreto de acordo com a Especificação da Linguagem Java: " Sempre que ocorre como o lado esquerdo em uma expressão de atribuição, é definitivamente não atribuído e definitivamente não atribuído antes da atribuição". Uma variável / parâmetro é sempre ou nunca efetivamente final. Mais explicitamente, se você não puder adicionar a finalpalavra-chave a uma declaração sem introduzir erros de compilação, ela não será efetivamente final . É o contrapositivo dessa declaração: "Se uma variável for efetivamente final, adicionar o modificador final à sua declaração não introduzirá nenhum erro em tempo de compilação".
AndrewF 5/06

Os comentários no código de exemplo estão incorretos por todos os motivos descritos no meu comentário. "Efetivamente final" não é um estado que pode mudar com o tempo.
AndrewF

@AndrewF, se não mudar ao longo do tempo, o que você acha que a última linha não compila? rem foi efetivamente final na linha 1 no método de cálculo. No entanto, na última linha, o compilador reclama que rem não é efectivamente finais
O Método Científico

Você está certo de que parte do código precisa ser removida do bloco de código para compilar, mas isso não reflete o comportamento do tempo de execução. No momento da compilação, você pode decidir se uma variável é efetivamente final ou não - com base nas especificações, é sempre efetivamente final ou nunca é efetivamente final. O compilador pode dizer olhando estaticamente como a variável é usada em todo o seu escopo. A propriedade não pode ser adquirida ou perdida enquanto o programa é executado. O termo é bem definido pelas especificações - confira as outras respostas, que explicam muito bem.
AndrewF 17/06/19

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 já foi dito, uma variável ou parâmetro cujo valor nunca é alterado após a inicialização é efetivamente final. No código acima, se você alterar o valor de xna classe interna FirstLevel, o compilador exibirá a mensagem de erro:

Variáveis ​​locais referenciadas a partir de uma expressão lambda devem ser finais ou efetivamente finais.


1

Se você pudesse adicionar o finalmodificador a uma variável local, ele seria efetivamente final.

Expressões lambda podem acessar

  • variáveis ​​estáticas,

  • variáveis ​​de instância,

  • efetivamente parâmetros finais do método, e

  • efetivamente variáveis ​​locais finais.

Fonte: OCP: Guia de Estudo do Oracle Certified Professional Java SE 8 Programmer II da Oracle, Jeanne Boyarsky, Scott Selikoff

Além disso,

Uma effectively finalvariável é uma variável cujo valor nunca é alterado, mas não é declarado com a finalpalavra - chave.

Fonte: Iniciando com Java: das estruturas de controle aos objetos (6ª edição), Tony Gaddis

Além disso, não esqueça o significado de finalque ele foi inicializado exatamente uma vez antes de ser usado pela primeira vez.


0

Declarar uma variável finalou não final, mas mantê-la efetivamente final pode resultar (depende do compilador) em bytecode diferente.

Vamos dar uma olhada em um pequeno exemplo:

    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
        }
    }

O bytecode correspondente do mainmétodo (Java 8u161 no 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

A tabela de número de linha correspondente:

 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 podemos ver o código-fonte em linhas 12, 13, 14não aparecem no código byte. Isso é porque ié truee não mudará seu estado. Portanto, este código está inacessível (mais nesta resposta ). Pela mesma razão, o código na linha 9também falha. O estado de inão precisa ser avaliado, pois é truecerto.

Por outro lado, embora a variável jseja efetivamente final, ela não é processada da mesma maneira. Não existem essas otimizações aplicadas. O estado de jé avaliado duas vezes. O bytecode é o mesmo, independentemente de jser efetivamente final .


Eu consideraria isso uma ineficiência do compilador, e não necessariamente uma que ainda seria verdadeira em compiladores mais novos. Em uma compilação perfeita, se uma variável for efetivamente final, ela gerará exatamente as mesmas otimizações que uma declarada final. Portanto, não confie na noção de que efetivamente final é automaticamente mais lento do que declarar algo final.
precisa saber é

@AndrewF Geralmente você está certo, o comportamento pode mudar. É por isso que escrevi " pode resultar (depende do compilador) em diferentes bytecodes ". Somente por causa da otimização ausente (bytecode diferente), eu não consideraria a execução mais lenta. Mas ainda é uma diferença no caso mostrado.
LuCio 6/06/19

0

A variável Efetivamente final é uma variável local que é:

  1. Não definido como final
  2. Atribuído a APENAS uma vez.

Enquanto uma variável final é uma variável que é:

  1. declarado com uma finalpalavra - chave.

-6

No entanto, a partir do Java SE 8, uma classe local pode acessar variáveis ​​e parâmetros locais do bloco anexo que são finais ou efetivamente finais.

Isso não começou no Java 8, eu uso isso há muito tempo. Este código usado (antes do java 8) é 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
    }
);

2
A declaração refere-se ao fato de que em <Java 8, apenas final variáveis ​​podem ser acessadas, mas no Java 8 também aquelas que são efetivamente finais.
Antti Haapala 01/01

Eu só vejo código que não funciona, independentemente de você estiver usando Java 7 ou Java 8.
Holger
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.