Uma expressão lambda cria um objeto no heap toda vez que é executado?

182

Quando itero uma coleção usando o novo açúcar sintático do Java 8, como

myStream.forEach(item -> {
  // do something useful
});

Isso não é equivalente ao snippet 'old syntax' abaixo?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});

Isso significa que um novo Consumerobjeto anônimo é criado na pilha toda vez que eu itera sobre uma coleção? Quanto espaço de pilha isso leva? Que implicações de desempenho ele tem? Isso significa que devo preferir usar o estilo antigo para loops ao iterar sobre grandes estruturas de dados de vários níveis?

Bastian Voigt
fonte
48
Resposta curta: não. Para lambdas sem estado (aqueles que não capturam nada do seu contexto lexical), apenas uma instância será criada (preguiçosamente) e armazenada em cache no site de captura. (Isto é como a implementação funciona; a especificação foi cuidadosamente escrito para permitir, mas não exigir, esta abordagem.)
Brian Goetz

Respostas:

158

É equivalente, mas não idêntico. Simplificando, se uma expressão lambda não capturar valores, será um singleton que será reutilizado em todas as chamadas.

O comportamento não está exatamente especificado. A JVM recebe grande liberdade sobre como implementá-la. Atualmente, a JVM da Oracle cria (pelo menos) uma instância por expressão lambda (ou seja, não compartilha instância entre diferentes expressões idênticas), mas cria singletons para todas as expressões que não capturam valores.

Você pode ler esta resposta para obter mais detalhes. Lá, eu não apenas dei uma descrição mais detalhada, mas também testei o código para observar o comportamento atual.


Isso é coberto por The Java® Language Specification, capítulo “ 15.27.4. Avaliação em tempo de execução de expressões lambda

Resumido:

Essas regras visam oferecer flexibilidade às implementações da linguagem de programação Java, na medida em que:

  • Um novo objeto não precisa ser alocado em todas as avaliações.

  • Objetos produzidos por diferentes expressões lambda não precisam pertencer a diferentes classes (se os corpos forem idênticos, por exemplo).

  • Todo objeto produzido pela avaliação não precisa pertencer à mesma classe (variáveis ​​locais capturadas podem estar embutidas, por exemplo).

  • Se uma "instância existente" estiver disponível, ela não precisará ter sido criada em uma avaliação lambda anterior (ela pode ter sido alocada durante a inicialização da classe envolvente, por exemplo).

Holger
fonte
24

Quando uma instância representando o lambda é criada com sensibilidade, depende do conteúdo exato do corpo do seu lambda. Ou seja, o fator principal é o que o lambda captura do ambiente lexical. Se ele não capturar nenhum estado variável de criação para criação, uma instância não será criada toda vez que o loop for-for inserido. Em vez disso, um método sintético será gerado em tempo de compilação e o site de uso lambda receberá apenas um objeto singleton que delega para esse método.

Observe também que esse aspecto depende da implementação e você pode esperar aprimoramentos e avanços futuros no HotSpot em direção a uma maior eficiência. Existem planos gerais para, por exemplo, criar um objeto leve sem uma classe correspondente completa, que possui informações suficientes para encaminhar para um único método.

Aqui está um artigo detalhado, bom e acessível sobre o tópico:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

Marko Topolnik
fonte
0

Você está passando uma nova instância para o forEachmétodo Toda vez que você faz isso, cria um novo objeto, mas não um para cada iteração de loop. A iteração é feita dentro do forEachmétodo usando a mesma instância de objeto 'callback' até que seja feita com o loop.

Portanto, a memória usada pelo loop não depende do tamanho da coleção.

Isso não é equivalente ao snippet 'old syntax'?

Sim. Tem pequenas diferenças em um nível muito baixo, mas não acho que você deva se preocupar com elas. As expressões Lamba usam o recurso invokedynamic em vez de classes anônimas.

aalku
fonte
Você tem alguma documentação especificando isso? É uma otimização bastante interessante.
A. Rama
Obrigado, mas e se eu tiver uma coleção de coleções de coleções, por exemplo, ao fazer uma pesquisa profunda em uma estrutura de dados em árvore?
Bastian Voigt
2
@ A. Rama Desculpe, não vejo a otimização. É o mesmo com ou sem lambdas e com ou sem loop forEach.
aalku
1
Não é realmente o mesmo, mas ainda será necessário um objeto por nível de aninhamento a qualquer momento, o que é insignificante. Cada nova iteração de um loop interno criará um novo objeto, provavelmente capturando o item atual do loop externo. Isso cria alguma pressão no GC, mas ainda não há nada com o que realmente se preocupar.
Marko Topolnik
4
@aalku: " Toda vez que você faz isso, cria um novo objeto ": Não de acordo com esta resposta de Holger e este comentário de Brian Goetz .
Lii 06/03