Foreach with Lambda does not work

Asked

Viewed 1,289 times

3

I have a list of objects I want to go through, but it’s giving error and I’m not understanding why:

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

the mistake is this

local variables referenced from a lambda Expression must be final or effectively final

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

    List <String> teste = new ArrayList<>();
    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
     teste.add(atendimentoFicha.getId)));
    });
  • Like jTxtLog and fichaAtend are declared and where they are used?

  • 1

    That’s exactly what the error says. You can’t iterate or modify variables from outside the foreach. They need to be "final". But by "final", I don’t really mean a variable declared as "final", but something that won’t have its value modified within the foreach. There is a very good answer on this link: http://stackoverflow.com/questions/20938095/difference-between-final-and-effectively-final

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 this stack frame will still exist. However, if the variable is declassified 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 within the anonymous class will always reflect the value of the stack frame. Thus, the above code could be rewritten in this way:

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 implementation of Amble. However, once you are forced to always declare final is very boring, the concept of final effect, 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 she actually has the modifier final. 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 (i.e., it cannot be implicitly considered as final), then the scheme 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 final effect, because at least one of them is a local variable that does not have the modifier final and which also undergoes modifications after having its value assigned for the first time.

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

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

  2. The second solution is to declare another variable final effect and assign the value of these variables:

    TipoDoFichaAtend fichaAtend2 = fichaAtend;
    TipoDoJTxtLog jTxtLog2 = jTxtLog;
    listaAtendimento.stream().forEach(atendimentoFicha ->
    {   
        fichaAtend2.getAtendimentosIndividuais().add(getAtendIndivChild(jTxtLog2, atendimentoFicha));
    });
    
  3. There are cases where the second solution is not adwquad, because it only works if the value of the variable is no longer modified in the external scope or if these modifications should not be reflected in the internal scope. It also doesn’t 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 a variable final effect. The guy AtomicReference is a good candidate for it:

    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.
    

    An alternative to the AtomicReference simpler, but more gambiarrosa and that is not thread-safe, is to use a single position array 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.
    
  4. The ultimate solution is to restructure your code so that it no longer needs to use the lambda. This situation is limited and cannot always be applied, but is frequent in the case of Amble which may be replaced by links for or Enhanced-for.

Finally, in the last example you gave, the only variable of the external scope that is used in the internal scope is the variable teste, that is final effect because its value is only assigned once. And because of this, the code in this example compiles.

  • That’s what I wanted to know. thank you very much... in case to solve this problem, I had to give up using this freshness of Lambda and use the for classico for (FichaAtendIndividual atendimentoFicha : listaAtendimento) {fichaAtend.getAtendimentosIndividuais().add(getAtendIndivChild(jTxtLog, atendimentoFicha));}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.