Foreach with Lambda does not work

2

I have a list of objects that I want to go through, but it is giving error and I am not understanding why:

    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
        fichaAtend.getAtendimentosIndividuais().add(getAtendIndivChild(jTxtLog, atendimentoFicha));
    });

The error is this

  

local variables referenced from lambda expression must be final or effectively final

Can someone explain to me why there is an error now if the code below does not occur?

    List <String> teste = new ArrayList<>();
    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
     teste.add(atendimentoFicha.getId)));
    });
    
asked by anonymous 12.11.2015 / 11:32

1 answer

4

Until Java 7, using external local scope variables within anonymous classes would not work if they were not final :

public void metodoQualquer() {
    int a = 5;
    int b = 7;
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(a + b); // Erro de compilação aqui!
        }
    });
    t.start();
    try {
        t.join();
    } catch (InterruptedException e) {
        // Ignora a exceção.
    }
}

The reason is that local variables are allocated in the stack frame of the local method call, and nothing guarantees that when the anonymous class method is executed, that stack frame will still exist. However, if the variable is declared final , then the compiler can synthesize a constructor of the anonymous class responsible for copying these values, and since they are final , then the value used inside the anonymous class will always reflect the value of the stack frame . So the above code could be rewritten like this:

public void metodoQualquer() {
    final int a = 5;
    final int b = 7;
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(a + b); // Agora não dá erro de compilação aqui.
        }
    });
    t.start();
    try {
        t.join();
    } catch (InterruptedException e) {
        // Ignora a exceção.
    }
}

Java 8 inherits this same rule in the lambdas implementation. However, since being forced to always declare final is very annoying, the concept of effectively final has emerged, which basically means that if the variable does not change its value, even if the modifier final is not present, so for all practical purposes it is as if it actually had the final modifier. So in Java 8 I can write the above code this way:

public void metodoQualquer() {
    int a = 5;
    int b = 7;

    // Não dá erro de compilação aqui, mesmo que o a e o b não tenham sido declarados como final.
    Thread t = new Thread(() -> System.out.println(a + b));
    t.start();
    try {
        t.join();
    } catch (InterruptedException e) {
        // Ignora a exceção.
    }
}

However, if the variable can be modified (that is, it can not be implicitly considered as final ), then the schema no longer works:

public void metodoQualquer() {
    int a = 5;
    int b = 7;

    // Erro de compilação aqui, a não é final ou effectively final.
    Thread t = new Thread(() -> a = a + b);
    t.start();
    try {
        t.join();
    } catch (InterruptedException e) {
        // Ignora a exceção.
    }
}

In your case, at least one of the variables fichaAtend or jTxtLog is not effectively , because at least one of them is a local variable that does not have the final modifier and that it also undergoes modifications after having first assigned its value.

There are four solutions to this. Choose the one that is best for you:

  • The first solution is to change the structure of the external scope to ensure that these variables are final or effectively .

  • The second solution is to declare another variable effectively and assign the value of these variables:

    TipoDoFichaAtend fichaAtend2 = fichaAtend;
    TipoDoJTxtLog jTxtLog2 = jTxtLog;
    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
        fichaAtend2.getAtendimentosIndividuais().add(getAtendIndivChild(jTxtLog2, atendimentoFicha));
    });
    
  • There are cases where the second solution is not adwanted, because it only works if the value of the variable is not further modified in the external scope or if these modifications are not to be reflected in the internal scope. It also does not work if the internal scope modifies the variables of the external scope. In this case, we have a third solution, which is to encapsulate the value in a mutable object stored in an effectively final variable. The type AtomicReference is a good candidate for this:

    AtomicReference<TipoDoFichaAtend> fichaAtend2 = new AtomicReference<>(fichaAtend);
    AtomicReference<TipoDoJTxtLog> jTxtLog2 = new AtomicReference<>(jTxtLog);
    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
        fichaAtend2.get().getAtendimentosIndividuais().add(getAtendIndivChild(jTxtLog2.get(), atendimentoFicha));
        jTxtLog2.set(oQueVoceQuiser); // Exemplo de modificação do conteúdo da variável.
    });
    TipoDoJTxtLog resultado = jTxtLog2.get(); // Trará o resultado após modificações no escopo interno.
    

    Another alternative to the simplest%, but more gambiarrosa and non-thread-safe , is to use an array of a single position to encapsulate the object :

    TipoDoFichaAtend[] fichaAtend2 = {fichaAtend};
    TipoDoJTxtLog[] jTxtLog2 = {jTxtLog};
    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
        fichaAtend2[0].getAtendimentosIndividuais().add(getAtendIndivChild(jTxtLog2[0], atendimentoFicha));
        jTxtLog2[0] = oQueVoceQuiser; // Exemplo de modificação do conteúdo da variável.
    });
    TipoDoJTxtLog resultado = jTxtLog2[0]; // Trará o resultado após modificações no escopo interno.
    
  • The last solution is to restructure your code so that it no longer needs to use lambda . This situation is limited and can not always be applied, but it is often the case in lambda that can be replaced by for or enhanced-for .

  • Finally, in the last example you gave, the only variable of the external scope that is used in the inner scope is the variable AtomicReference , which is effectively because its value is only assigned only once. And because of that, the code in this example compiles.

        
    12.11.2015 / 12:07