¿Existe un beneficio de rendimiento al usar la sintaxis de referencia del método en lugar de la sintaxis lambda en Java 8?

51

¿Las referencias de método omiten la sobrecarga de la envoltura lambda? ¿Podrían ellos en el futuro?

De acuerdo con el Tutorial de Java sobre referencias de métodos :

  

A veces ... una expresión lambda no hace más que llamar a un método existente.   En esos casos, a menudo es más claro referirse al método existente por   nombre. Las referencias de método le permiten hacer esto; son compactos,   Expresiones lambda fáciles de leer para métodos que ya tienen un nombre.

Prefiero la sintaxis lambda a la sintaxis de referencia del método por varias razones:

Las Lambdas son más claras

A pesar de las afirmaciones de Oracle, me parece que la sintaxis lambda es más fácil de leer que la referencia del método del objeto, porque la sintaxis de la referencia del método es ambigua:

Bar::foo

¿Está llamando a un método estático de un solo argumento en la clase de x y lo está pasando x?

x -> Bar.foo(x)

¿O estás llamando a un método de instancia de cero argumentos en x?

x -> x.foo()

La sintaxis de referencia del método podría representar cualquiera de las dos. Oculta lo que realmente está haciendo tu código.

Las Lambdas son más seguras

Si hace referencia a Bar :: foo como método de clase y luego Bar agrega un método de instancia con el mismo nombre (o viceversa), su código ya no se compilará.

Puedes usar lambdas consistentemente

Puedes envolver cualquier función en un lambda, por lo que puedes usar la misma sintaxis de manera consistente en todas partes. La sintaxis de referencia de método no funcionará en los métodos que toman o devuelven matrices primitivas, lanzan excepciones verificadas, o tienen el mismo nombre de método usado como una instancia y un método estático (porque la sintaxis de referencia de método es ambigua sobre qué método se llamaría) . No funcionan cuando tienes métodos sobrecargados con la misma cantidad de argumentos, pero no debes hacerlo de todos modos (consulta el artículo 41 de Josh Bloch), por lo que no podemos compararlo con las referencias de los métodos.

Conclusión

Si no hay una penalización de rendimiento por hacerlo, estoy tentado de desactivar la advertencia en mi IDE y utilizar la sintaxis lambda de forma coherente sin agregar la referencia de método ocasional a mi código.

P.S.

Ni aquí ni allí, pero en mis sueños, las referencias de los métodos de objetos se parecen más a esto y aplican invoke-dynamic contra el método directamente en el objeto sin un contenedor lambda:

_.foo()
    
pregunta GlenPeterson 26.03.2015 - 16:16

3 respuestas

11

En muchas escenas, creo que lambda y el método de referencia son equivalentes. Pero la lambda ajustará el destino de invocación por el tipo de interfaz declarante.

Por ejemplo

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);
    }
}

Verás la salida de la consola del seguimiento de pila.

En lambda() , el método que llama a target() es lambda$lambda$0(InvokeTest.java:20) , que tiene información de línea rastreable. Obviamente, ese es el lambda que escribes, el compilador genera un método anónimo para ti. Y luego, la persona que llama al método lambda es algo así como InvokeTest$$Lambda$2/1617791695.run(Unknown Source) , es decir, la llamada invokedynamic en JVM, significa que la llamada está vinculada al método generado .

En methodReference() , el método que llama a target() es directamente el InvokeTest$$Lambda$1/758529971.run(Unknown Source) , lo que significa que la llamada está directamente vinculada al método InvokeTest::target .

Conclusión

Por encima de todo, compare con el método de referencia, usar la expresión lambda solo causará una más llamada de método al método de generación desde lambda.

    
respondido por el JasonMing 11.11.2015 - 11:54
23

Se trata de metafactory

En primer lugar, la mayoría de las referencias de métodos no necesitan desugarse por la metafactor lambda, simplemente se usan como método de referencia. Debajo de la sección "Sugarda body sugaring" de la Traducción de Lambda Expressions ("TLE") artículo:

  

En igualdad de condiciones, los métodos privados son preferibles a los no privados, los métodos estáticos son preferibles a los métodos de instancia, es mejor si los cuerpos lambda se encuentran en la clase más interna en la que aparece la expresión lambda, las firmas deben coincidir con la firma del cuerpo de lambda, los argumentos adicionales deben incluirse en la parte frontal de la lista de argumentos para los valores capturados, y no desecharían las referencias de los métodos. Sin embargo, hay casos de excepción en los que es posible que tengamos que desviarnos de esta estrategia de línea de base.

Esto se resalta más adelante en "The Lambda Metafactory" de TLE:

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
     

El argumento impl identifica el método lambda, ya sea un cuerpo lambda desugared o el método nombrado en una referencia de método.

Las referencias estáticas ( Integer::sum ) o de método de instancia ilimitada ( Integer::intValue ) son las referencias "más simples" o las más "convenientes", en el sentido de que pueden manejarse de manera óptima mediante una "ruta rápida" variante de metáfora sin desgafaring . Esta ventaja se señala de manera útil en las "Variantes metafactorias" de TLE:

  

Al eliminar los argumentos donde no son necesarios, los archivos de clase se vuelven más pequeños. Y la opción de ruta rápida baja la barra para que la VM intrinsifique la operación de conversión lambda, lo que le permite ser tratado como una operación de "boxeo" y facilita las optimizaciones de unbox.

Naturalmente, una referencia de método de captura de instancia ( obj::myMethod ) debe proporcionar la instancia limitada como un argumento al identificador del método para la invocación, lo que puede significar la necesidad de desugaring usando métodos de "puente".

Conclusión

No estoy exactamente seguro de qué es el "envoltorio" de lambda al que estás insinuando, pero a pesar de que el resultado final del uso de tus lambdas definidas por el usuario o las referencias de métodos son los mismos, la manera Eso se alcanza parece ser bastante diferente, y puede ser diferente en el futuro si ese no es el caso ahora. Por lo tanto, supongo que es más probable que no sea que las referencias de los métodos puedan ser manejadas de una manera más óptima por la metafactoria.

    
respondido por el h.j.k. 29.05.2015 - 20:47
0

Hay una consecuencia bastante seria al usar expresiones lambda que pueden afectar el rendimiento.

Cuando declara una expresión lambda, está creando un cierre sobre el ámbito local.

¿Qué significa esto y cómo afecta el rendimiento?

Bueno, me alegra que hayas preguntado. Significa que cada una de esas pequeñas expresiones lambda es una pequeña clase interna anónima y eso significa que lleva consigo una referencia a todas las variables que están en el mismo ámbito de la expresión lambda.

Esto significa también la referencia this de la instancia del objeto y todos sus campos. Dependiendo de la sincronización de la invocación real de la lambda, esto puede equivaler a una fuga de recursos bastante significativa ya que el recolector de basura no puede dejar de lado estas referencias siempre que el objeto que contiene a la lambda esté vivo ...

    
respondido por el Roland Tepp 12.11.2015 - 22:55

Lea otras preguntas en las etiquetas