Java 8 presenta nuevas características importantes del lenguaje, como las expresiones lambda.
¿Estos cambios en el lenguaje van acompañados de cambios tan significativos en el código de bytes compilado que evitarían que se ejecute en una máquina virtual Java 7 sin utilizar algún retrotranslator?
Respuestas:
No, el uso de las funciones 1.8 en su código fuente requiere que apunte a una VM 1.8. Acabo de probar la nueva versión de Java 8 e intenté compilar
-target 1.7 -source 1.8
, y el compilador se niega:fuente
Los métodos predeterminados requieren tales cambios en el bytecode y la JVM que hubieran sido imposibles de hacer en Java 7. El verificador de bytecode de Java 7 y versiones posteriores rechazará las interfaces con los cuerpos de los métodos (excepto el método de inicializador estático). Intentar emular métodos predeterminados con métodos estáticos en el lado de la persona que llama no produciría los mismos resultados, porque los métodos predeterminados pueden anularse en subclases. Retrolambda tiene un soporte limitado para los métodos predeterminados de backport, pero nunca puede ser completamente backport porque realmente requiere nuevas características de JVM.
Lambdas podría ejecutarse en Java 7 tal cual, si las clases de API necesarias simplemente existieran allí. La instrucción invocada dinámica existe en Java 7, pero habría sido posible implementar lambdas para que genere las clases lambda en tiempo de compilación (las primeras compilaciones de JDK 8 lo hicieron de esa manera) en cuyo caso funcionaría en cualquier versión de Java. (Oracle decidió usar invocados dinámicos para lambdas para pruebas futuras; tal vez algún día JVM tendrá funciones de primera clase, por lo que puede invocarse dinámico para usarlos en lugar de generar una clase para cada lambda, mejorando así el rendimiento). Lo que hace Retrolambda es que procesa todas esas instrucciones dinámicas invocadas y las reemplaza con clases anónimas; lo mismo que hace Java 8 en tiempo de ejecución cuando una lamdba invocada dinámicamente se llama por primera vez.
Repetir anotaciones es solo azúcar sintáctico. Son bytecode compatibles con versiones anteriores. En Java 7 solo necesitaría implementar los métodos de ayuda (por ejemplo, getAnnotationsByType ) que ocultan los detalles de implementación de una anotación de contenedor que contiene las anotaciones repetidas.
AFAIK, las anotaciones de tipo solo existen en el momento de la compilación, por lo que no deberían requerir cambios de bytecode, por lo que solo cambiar el número de versión de bytecode de las clases compiladas de Java 8 debería ser suficiente para que funcionen en Java 7.
Los nombres de los parámetros del método existen en el bytecode con Java 7, por lo que también es compatible. Puede obtener acceso a ellos leyendo el código de bytes del método y mirando los nombres de las variables locales en la información de depuración del método. Por ejemplo, Spring Framework hace exactamente eso para implementar @PathVariable , por lo que probablemente haya un método de biblioteca al que podría llamar. Debido a que los métodos de interfaz abstractos no tienen un cuerpo de método, esa información de depuración no existe para los métodos de interfaz en Java 7, y AFAIK tampoco en Java 8.
Las otras características nuevas son en su mayoría API nuevas, mejoras en HotSpot y herramientas. Algunas de las nuevas API están disponibles como bibliotecas de terceros (p. Ej. ThreeTen-Backport y streamsupport ).
En resumen, los métodos predeterminados requieren nuevas funciones de JVM, pero las otras funciones del lenguaje no. Si desea usarlos, deberá compilar el código en Java 8 y luego transformar el bytecode con Retrolambda al formato Java 5/6/7 . Como mínimo, la versión de bytecode debe cambiarse, y javac no
-source 1.8 -target 1.7
lo permite, por lo que se requiere un retrotranslator.fuente
Hasta donde yo sé, ninguno de estos cambios en JDK 8 requirió la adición de nuevos códigos de bytes. Parte de la instrumentación lambda se realiza utilizando
invokeDynamic
(que ya existe en JDK 7). Entonces, desde el punto de vista del conjunto de instrucciones JVM, nada debería hacer que la base de código sea incompatible. Sin embargo, hay muchas mejoras de compilación y API asociadas que podrían hacer que el código de JDK 8 sea difícil de compilar / ejecutar en JDK anteriores (pero no lo he intentado).Tal vez el siguiente material de referencia pueda ayudar de alguna manera a enriquecer la comprensión de cómo se están instrumentando los cambios relacionados con lambda.
Estos explican en detalle cómo se instrumentan las cosas debajo del capó. Quizás pueda encontrar la respuesta a sus preguntas allí.
fuente
class C extends A with B
, se lleva a cabo con interfaces normalesA
yB
y clases de compañíaA$class
yB$class
. La claseC
simplemente reenvía los métodos a las clases complementarias estáticas. Los auto-tipos no se aplican en absoluto, las lambdas se trasladan en tiempo de compilación a clases internas abstractas, por lo que es unanew D with A with B
expresión. La coincidencia de patrones es un conjunto de estructuras if-else. Devoluciones no locales? mecanismo try-catch de la lambda. ¿Queda algo? (Curiosamente, mi scalac dice que 1.6 es el predeterminado)Si está dispuesto a utilizar un "retrotranslator", pruebe el excelente Retrolambda de Esko Luontola: https://github.com/orfjackal/retrolambda
fuente
Puede hacerlo,
-source 1.7 -target 1.7
entonces se compilará. Pero no se compilará si tiene características específicas de Java 8 como lambdasfuente
-source 1.7
lo que no volará.