JavaScript x Java - closures e lambdas

Em geral as linguagens de programação são bem parecidas e por esse motivo acho fácil aprender novas linguagens. E o que dizer então quando uma se chama Java e a outra JavaScript? :smiley:

Ultimamente tenho programado bastante em JavaScript. Apesar de não conhecer ainda todos os detalhes da linguagem, tenho me virado bem, porém, hoje perdi um certo tempo programando em JavaScript como se estivesse programando em Java.

Simplificação do código em JavaScript:

var i = 0;
var ar = [];

while (i < 2)
{
    var n = i;
    
    ar.push(function() {
        return n;
    });

    ++i;
}

console.log(ar[0]());   // 1
console.log(ar[1]());   // 1

Neste trecho de código acima, esperava que imprimisse as linhas 0 e 1, como no código abaixo equivalente em Java, porém imprime 1 e 1.

Código em Java (8) supostamente equivalente:

int i = 0;
ArrayList<Supplier<Integer>> ar = new ArrayList<>();

while (i < 2)
{
    int n = i;

    ar.add(() -> n);
    ++i;
}

System.out.println(ar.get(0).get()); // 0
System.out.println(ar.get(1).get()); // 1

Enquanto que o código Java carrega o valor de “n” (“closure” - entre aspas porque no Java isso não é bem uma closure) para a expressão lambda com o valor de cada iteração do while, o código JavaScript carrega apenas uma variável “n”, que é alterada em cada iteração. Isso acontece porque as regras de escopo de variáveis do JavaScript são totalmente diferentes de Java.

O código JavaScript abaixo que é equivalente ao código Java e retorna 0 e 1:

var i = 0;
var ar = [];

while (i < 2)
{
    (function(n) {
        ar.push(function() {
            return n;
        })
    })(i);

    ++i;
}

console.log(ar[0]()); // 0
console.log(ar[1]()); // 1

Passando “i” para o parâmetro “n” da função anônima, criamos um novo escopo.