Existe um benefício de desempenho em usar a sintaxe de referência do método em vez da sintaxe lambda no Java 8?

56

As referências de método ignoram a sobrecarga do wrapper lambda? Eles podem no futuro?

De acordo com o Tutorial Java sobre referências de método :

Às vezes ... uma expressão lambda não faz nada além de chamar um método existente. Nesses casos, geralmente é mais claro fazer referência ao método existente por nome. As referências de método permitem que você faça isso; elas são expressões lambda compactas e fáceis de ler para métodos que já possuem um nome.

Prefiro a sintaxe lambda à sintaxe de referência do método por vários motivos:

Lambdas são mais claras

Apesar das alegações da Oracle, acho a sintaxe lambda mais fácil de ler do que a referência de método de objeto em abreviação, porque a sintaxe de referência de método é ambígua:

Bar::foo

Você está chamando um método estático de um argumento na classe de x e passando x?

x -> Bar.foo(x)

Ou você está chamando um método de instância de argumento zero em x?

x -> x.foo()

A sintaxe de referência do método pode substituir qualquer um. Ele oculta o que seu código está realmente fazendo.

Lambdas são mais seguras

Se você referenciar Bar :: foo como um método de classe e Bar posteriormente adicionar um método de instância com o mesmo nome (ou vice-versa), seu código não será mais compilado.

Você pode usar lambdas consistentemente

Você pode agrupar qualquer função em uma lambda - para poder usar a mesma sintaxe de maneira consistente em todos os lugares. A sintaxe de referência do método não funciona em métodos que recebem ou retornam matrizes primitivas, lançam exceções verificadas ou têm o mesmo nome de método usado como uma instância e um método estático (porque a sintaxe de referência do método é ambígua sobre qual método seria chamado) . Eles não funcionam quando você sobrecarregou métodos com o mesmo número de argumentos, mas você não deve fazer isso de qualquer maneira (consulte o item 41 de Josh Bloch), para que não possamos impedir isso de fazer referência a métodos.

Conclusão

Se não houver penalidade de desempenho por fazer isso, sou tentado a desativar o aviso no meu IDE e usar a sintaxe lambda de forma consistente, sem borrifar a referência ocasional do método no meu código.

PS

Nem aqui nem ali, mas nos meus sonhos, as referências de método de objeto se parecem mais com isso e aplicam invoke-dynamic contra o método diretamente no objeto sem um wrapper lambda:

_.foo()
GlenPeterson
fonte
2
Isso não responde à sua pergunta, mas há outros motivos para o uso de fechamentos. Na minha opinião, muitos deles superam a pequena sobrecarga de uma chamada de função adicional.
9
Acho que você está se preocupando com o desempenho prematuramente. Na minha opinião, o desempenho deve ser uma consideração secundária para o uso de uma referência de método; é tudo sobre expressar sua intenção. Se você precisar passar uma função em algum lugar, por que não passar a função em vez de outra função que a delegue? Parece estranho para mim; como você não entende que funções são coisas de primeira classe, elas precisam de um acompanhante anônimo para movê-las. Mas, para abordar sua pergunta mais diretamente, não me surpreenderia se uma referência de método pudesse ser implementada como uma chamada estática.
Doval 26/03
7
.map(_.acceptValue()): Se não me engano, isso se parece muito com a sintaxe do Scala. Talvez você esteja apenas tentando usar o idioma errado.
Giorgio
2
"A sintaxe de referência do método não funciona em métodos que retornam nulos" - Como assim? Eu estou passando System.out::printlna forEach()todo o tempo ...?
Lukas Eder
3
"A sintaxe de referência do método não funciona em métodos que recebem ou retornam matrizes primitivas, lançam exceções verificadas ..." Isso não está correto. Você pode usar referências de método e expressões lambda para esses casos, desde que a interface funcional em questão permita.
Stuart Marks

Respostas:

12

Em muitos cenários, acho que lambda e referência de método são equivalentes. Mas o lambda envolverá o destino de chamada pelo tipo de interface declarante.

Por exemplo

public class InvokeTest {

    private static void invoke(final Runnable r) {
        r.run();
    }

    private static void target() {
        new Exception().printStackTrace();
    }

    @Test
    public void lambda() throws Exception {
        invoke(() -> target());
    }

    @Test
    public void methodReference() throws Exception {
        invoke(InvokeTest::target);
    }
}

Você verá o console exibir o rastreamento de pilha.

Em lambda(), a chamada do método target()é lambda$lambda$0(InvokeTest.java:20), que possui informações de linha rastreáveis. Obviamente, esse é o lambda que você escreve, o compilador gera um método anônimo para você. E então, o responsável pela chamada do método lambda é algo como InvokeTest$$Lambda$2/1617791695.run(Unknown Source), ou seja, a invokedynamicchamada na JVM, significa que a chamada está vinculada ao método gerado .

Em methodReference(), a chamada do método target()é diretamente a InvokeTest$$Lambda$1/758529971.run(Unknown Source), significa que a chamada está diretamente vinculada ao InvokeTest::targetmétodo .

Conclusão

Acima de tudo, compare com a referência do método, usar a expressão lambda causará apenas mais uma chamada de método para o método gerador a partir do lambda.

JasonMing
fonte
11
A resposta abaixo sobre a meta-fábrica é um pouco melhor. Eu adicionei alguns comentários úteis que sejam relevantes para ele (ou seja, como implementações como Oracle / OpenJDK uso ASM para gerar a classe de wrapper objetos necessários para criar instâncias lambda / punho método.
Ajax
29

É tudo sobre a meta - fábrica

Primeiro, a maioria das referências de método não precisa de remoção de açúcar pela meta-fábrica lambda, elas são simplesmente usadas como método de referência. Na seção "Adição de açúcar no corpo da lambda" do artigo Tradução de expressões lambda ("TLE"):

Sendo todas as coisas iguais, métodos privados são preferíveis a não privados, métodos estáticos são preferíveis a métodos de instância, é melhor que os corpos lambda sejam adicionados na classe mais interna na qual a expressão lambda aparece, as assinaturas devem corresponder à assinatura do corpo do lambda, extra os argumentos devem ser anexados na frente da lista de argumentos para obter os valores capturados e não desejariam nenhuma referência ao método. No entanto, há casos de exceção em que talvez tenhamos que nos afastar dessa estratégia de linha de base.

Isso é destacado ainda mais abaixo em "The Lambda Metafactory":

metaFactory(MethodHandles.Lookup caller, // provided by VM
            String invokedName,          // provided by VM
            MethodType invokedType,      // provided by VM
            MethodHandle descriptor,     // lambda descriptor
            MethodHandle impl)           // lambda body

O implargumento identifica o método lambda, um corpo lambda sem açúcar ou o método nomeado em uma referência de método.

As referências de Integer::summétodo de instância estática ( ) ou ilimitada ( Integer::intValue) são as 'mais simples' ou as mais 'convenientes', no sentido de que elas podem ser tratadas de maneira ideal por uma variante metafábrica de 'caminho rápido' sem a remoção de açúcar . Essa vantagem é útil em "Variantes meta-fábricas" do TLE:

Ao eliminar argumentos onde eles não são necessários, os arquivos de classe se tornam menores. E a opção de caminho rápido reduz a barra para a VM intrinsecificar a operação de conversão lambda, permitindo que ela seja tratada como uma operação de "encaixotamento" e facilitando as otimizações da unbox.

Naturalmente, um método de captura de instância reference ( obj::myMethod) precisa fornecer a instância delimitada como um argumento para o identificador de método para invocação, o que pode significar a necessidade de desaprovar usando métodos 'bridge'.

Conclusão

Não sei exatamente qual é o 'wrapper' do lambda que você está insinuando, mas mesmo que o resultado final do uso de lambdas ou referências de método definidas pelo usuário seja o mesmo, a maneira alcançada parece ser bem diferente e pode ser diferente no futuro, se esse não for o caso agora. Portanto, suponho que seja mais provável que as referências de métodos possam ser tratadas de maneira mais otimizada pela metafábrica.

hjk
fonte
11
Essa é a resposta mais relevante, pois realmente aborda o mecanismo interno necessário para criar um lambda. Como alguém que realmente depurou o processo de criação do lambda (para descobrir como gerar IDs estáveis ​​para uma determinada referência lambda / método para que eles possam ser removidos dos mapas), achei bastante surpreendente o quanto de trabalho é feito para criar um instância de um lambda. Oracle / OpenJDK uso ASM para gerar dinamicamente classes que, se necessário, perto sobre qualquer variável referenciada no seu lambda (ou o qualificador instância de uma referência de método) ...
Ajax
11
... Além de fechar as variáveis, os lambdas, em particular, também criam um método estático que é injetado na classe declarante, para que o lambda criado tenha algo a ser chamado para mapear nas funções que você deseja chamar. Uma referência de método com um qualificador de instância tomará essa instância como um parâmetro para esse método; Não testei se uma referência this :: method em uma classe privada ignora esse fechamento, mas testei que os métodos estáticos, de fato, ignoram o método intermediário (e apenas criam um objeto para estar em conformidade com o destino de chamada no site de chamada).
Ajax
11
Presumivelmente, um método privado também não precisará de despacho virtual, enquanto que qualquer coisa mais pública precisará ser encerrada sobre a instância (por meio de um método estático gerado) para que ele possa invocar o virtual na instância real para garantir que ele observe substituições. TL; DR: as referências de método fecham com menos argumentos, portanto vazam menos e exigem menos geração de código dinâmica.
Ajax
3
Último comentário com spam ... A geração de classe dinâmica é bastante pesada. Provavelmente ainda melhor do que carregar uma classe anônima no carregador de classes (já que as partes mais pesadas são feitas apenas uma vez), mas você ficaria realmente surpreso com quanto trabalho é necessário para criar uma instância lambda. Seria interessante ver referências reais de lambdas versus referências de método versus classes anônimas. Observe que duas lambdas ou referências de método que fazem referência às mesmas coisas, mas em locais diferentes criam classes diferentes em tempo de execução (mesmo dentro do mesmo método, logo após o outro).
Ajax
0

Há uma consequência bastante séria ao usar expressões lambda que podem afetar o desempenho.

Ao declarar uma expressão lambda, você está criando um fechamento sobre o escopo local.

O que isso significa e como isso afeta o desempenho?

Bem, eu estou feliz que você perguntou. Isso significa que cada uma dessas pequenas expressões lambda é uma pequena classe interna anônima e significa que ela carrega uma referência a todas as variáveis ​​que estão no mesmo escopo da expressão lambda.

Isso significa também thisreferência da instância do objeto e de todos os seus campos. Dependendo do momento da chamada real do lambda, isso pode resultar em um vazamento de recurso bastante significativo, pois o coletor de lixo não pode deixar de lado essas referências enquanto o objeto que está mantendo um lambda ainda estiver vivo ...

Roland Tepp
fonte
O mesmo vale para referências de método não estático.
Ajax
12
As lambdas Java não são estritamente encerradas. Nem são classes internas anônimas. Eles NÃO carregam uma referência a todas as variáveis ​​no escopo em que são declaradas. Eles apenas carregam uma referência às variáveis ​​que realmente fazem referência. Isso também se aplica ao thisobjeto que pode ser referenciado. Um lambda, portanto, não vaza recursos simplesmente por ter espaço para eles; ele se apega apenas aos objetos de que precisa. Por outro lado, uma classe interna anônima pode vazar recursos, mas nem sempre o faz. Veja este código para um exemplo: a.blmq.us/2mmrL6v
squid314
Essa resposta está simplesmente errada
Stefan Reich
Obrigado @StefanReich, Isso foi muito útil!
Roland Tepp 22/10