Los Lambdas de Java 8 NO son azucar sintactico para classes internas anonimas... invokedynamic??

Como me encanta seguir a la comunidad por medio de twitter (si, soy un stalker; so what?) pues un dia recuerdo que se discutia mucho que los Lambdas de Java 8 parecían ser solo azúcar sintáctico para clases anónimas internas (o como sea que se diga anonymous inner classses).

Esto es porque Java 8 requiere que para crear un Lambda que captura un valor (i.e. que referencia un valor fuera del cuerpo del lambda), dicho valor tiene que ser efectivamente final.

¿Que es efectivamente final? Pues que no haya diferencia si se pone un final en la variable o no.

Pero hoy resulta que me topo con este articulo en InfoQ que me dice que nop, no es azúcar sintáctico sino algo como crear metodos sinteticos y puede cambiar en el futuro.

Lo que no me queda claro (y espero que me ayuden con esto) es que exactamente hace el invokedynamic en el bytecode para la JVM?

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Java desde la versión 1.2

Java desde la versión 1.2 soporta closures pero la sintaxis es la de clases anónimas, se diseño así por que se consideraba que usar apuntadores a métodos era un mal diseño. De hecho había una "carta" criticando a Visual J++ (aunque no decían abiertamente pero era el lenguaje que había copiado casi todo de Java pero incluido cosas como esas para ser compatible con Visual Basic y otros de la familia ) por incluir esa funcionalidad. La demanda que Sun le ganó a Microsoft dejó atrás toda esa discusión y Java enfocó sus mejoras en el desempeño y bibliotecas y en menor grado funcionalidad faltante como los generics.

La idea de clases anónimas es bastante natural siguiendo el conceptos de clases, objetos etc. Si se desea tener un callback, se pasa una clase interna anónima ( que por ser interna tenía acceso al contexto donde fue creado, justo la definición de un closure )

Pero con el advenimiento de los nuevos lenguajes de programación y la nueva capacidad de procesamiento de las máquinas actuales, usar lenguajes de programación dinámicos dejo de ser un lujo (tardaban enormidades en ejecutarse) y Java empezó a ser demasiado engorroso para escribir, especialmente porque se comparaba la legibilidad con la de lenguajes como Ruby y Python. Han habido otros lenguajes desde siempre con esas características y otras más pero siempre permanecieron como lenguajes raros u obscuros o impracticos.

Con la presión de adaptarse a nuevos estilos y con el crecimiento de lenguajes que resolvían carencias como esta ( Groovy nació como la versión de Ruby con sintáxis de Java para la JVM ) Java finalmente accedió a modificar el lenguaje para incluir closures.

Un primer approach fue precisamente usar syntactic sugar y usar clases anónimas por debajo, de hecho es como Groovy mismo lo conseguía, bastaba implementar una interfaz que tuviera el número de parámetros necesario, algo como:

 

Cuando se usaba una closure, por debajo se tomaba alguna de estas interfaces y el código generado podía usar este tipo de dato y poder funcionar en la JVM.

 

Otra forma era simplemente usar reflection (   ) y dejar todo al runtime.

La razón por la cual se necesitaría implementar una interfaz, es porque la JVM para llamar métodos tiene las instrucciones: invokeinterface, invokevirtual e invokestatic. La primea dice a que interfaz pertenece el método, la segunda te permite llamar un método siempre y cuando sepas a que clase pertenece y en tiempo de ejecución le puedes proveer el objeto concreto, esta es la instrucción que se usa yo diría el 80% en código Java "normal", la JVM ya compiló y sabe que método esperar. Y la última es para llamar métodos concretos de una clase donde se sabe exactamente que clase y que métodos se va a ejecutar ( esta es la que se usa cuando se ejecutan métodos de clase, los que se marcan con el modificador  )

Existen otras instrucciones además de estas como invokeinterface e invokespecial y probablemente haya más, pero esas son las más usadas para ejecutar métodos.

Usar reflection es muy costoso ( o al menos más costoso cuando se busca el método ) porque se impacta el desempeño al buscar el método a invocar ( al contrario de solo invocarlo ) las siguientes llamadas tienen el método ya como variable o referencia y solo lo invocan y ahí el desempeño es casi idéntico. De todas formas casi todos los frameworks de Java hacen fuerte uso del reflection así que tampoco es para espantarse.

En Java 7 se introdujo una nueva instrucción en la JVM   que básicamente permite ejecutar una función cuyo origen se desconoce, con invokestatic sabemos clase en concreto y método se va a ejectuar, con invokevirtual sabemos que clase o que jerarquía de clase contiene el método y obvio el método mismo, con invokedynamic solo se conoce el método que se va a ejecutar, pero no se sabe que que clase pertenece ( o mejor aún) si pertenece a una clase, algo que es muy benéfico no solo para los lambdas en Java 8 sino para los lenguajes existentes que usen funciones como ciudadanos de primer orden ( que los puedan pasar como parámetros pues ). Cuando se codifica algo en Java el compilador lo traduce a instrucciones de la JVM, supongamos que hacer una invocación como la anterior requiere 5 o 6 instrucciones como ( notese que no tengo idea de como es el bytecode en realidad eh)

 

Mientras que usando invokedynamic esto puede ser una sola instrucción:

 

Porque en el primer caso se busca el objeto, se pone como variable y finalmente se ejecuta el método, en el segundo caso simplemente se ejecuta el método.

Y pues ya

TL;DR invokedynamic permite ejecutar una función o método sin tener que conocer a que clase o interfaz pertenece. El agregar una nueva instrucción en la JVM permite que menos instrucciones tengan que ser ejecutadas lo cual se traduce en mayor rapidez.

Más info:

Imagen de ElderMael

@OscarRyzPorque en el

@OscarRyz

Porque en el primer caso se busca el objeto, se pone como variable y finalmente se ejecuta el método, en el segundo caso simplemente se ejecuta el método.

Ujules, entonces puedo hacer duck typing a nivel bytecode? Digo, ¿Si tengo un método   en dos clases distintas y sin un ancestro común (y que no defina el método) solo lo mando llamar independientemente de la clase en donde lo quiero llamar? ¡Que magía tan negra y arcana!

Y pues ya

LoL. Gracias por tomarte el tiempo en explicarme esto.

Ahora que lo pienso, siempre descargaba Groovy y hacían mucha referencia a ese invokedynamic.

Imagen de ElderMael

Rascandole mas...

Ahora que tengo curiosidad sobre como funciona la JVM a nivel bytecode, encontre este articulo del desarrollador del JRuby; resulta y pasa que el propuso el JSR para la instrucción.

Voy a ver si el JSR quedo como lo explica ahí. Probablemente no.

Oye Miguel... tal vez quieras

Oye Miguel... tal vez quieras echarle un vistazo a este documentito (desde la pág. 496). ;)

Imagen de ElderMael

Gracias, leyendo :3

Gracias, leyendo :3