Java 8 introduit de nouvelles fonctionnalités de langage importantes telles que les expressions lambda.
Ces changements dans le langage sont-ils accompagnés de changements si importants dans le bytecode compilé qui l'empêcheraient d'être exécuté sur une machine virtuelle Java 7 sans utiliser un rétrotranslateur?
Réponses:
Non, l'utilisation des fonctionnalités 1.8 dans votre code source vous oblige à cibler une VM 1.8. J'ai juste essayé la nouvelle version de Java 8 et essayé de compiler avec
-target 1.7 -source 1.8
, et le compilateur refuse:la source
Les méthodes par défaut nécessitent de telles modifications du bytecode et de la JVM qu'elles auraient été impossibles à faire sur Java 7. Le vérificateur de bytecode de Java 7 et inférieur rejettera les interfaces avec les corps de méthode (à l'exception de la méthode d'initialisation statique). Essayer d'émuler les méthodes par défaut avec des méthodes statiques du côté appelant ne produirait pas les mêmes résultats, car les méthodes par défaut peuvent être remplacées dans les sous-classes. Retrolambda a une prise en charge limitée des méthodes de rétroportage par défaut, mais il ne peut jamais être complètement rétroporté car il nécessite vraiment de nouvelles fonctionnalités JVM.
Lambdas pourrait s'exécuter sur Java 7 tel quel, si les classes d'API nécessaires y existaient. L'instruction invokedynamic existe sur Java 7, mais il aurait été possible d'implémenter des lambdas pour qu'elle génère les classes lambda au moment de la compilation (les premières versions de JDK 8 l'ont fait de cette façon), auquel cas cela fonctionnerait sur n'importe quelle version de Java. (Oracle a décidé d'utiliser invokedynamic pour les lambdas pour la vérification future; peut-être qu'un jour JVM aura des fonctions de première classe, alors invokedynamic peut être changé pour les utiliser au lieu de générer une classe pour chaque lambda, améliorant ainsi les performances.) Ce que fait Retrolambda est qu'il traite toutes ces instructions dynamiques invoquées et les remplace par des classes anonymes; la même chose que ce que fait Java 8 lors de l'exécution lorsqu'un lamdba invokedynamic est appelé la première fois.
Répéter des annotations n'est que du sucre syntaxique. Ils sont compatibles avec le bytecode avec les versions précédentes. Dans Java 7, vous devez simplement implémenter vous-même les méthodes d'assistance (par exemple getAnnotationsByType ) qui cachent les détails d'implémentation d'une annotation de conteneur qui contient les annotations répétées.
AFAIK, les annotations de type n'existent qu'au moment de la compilation, elles ne devraient donc pas nécessiter de changements de bytecode, donc le simple fait de changer le numéro de version de bytecode des classes compilées Java 8 devrait être suffisant pour les faire fonctionner sur Java 7.
Les noms de paramètres de méthode existent dans le bytecode avec Java 7, donc c'est également compatible. Vous pouvez y accéder en lisant le bytecode de la méthode et en regardant les noms des variables locales dans les informations de débogage de la méthode. Par exemple, Spring Framework fait exactement cela pour implémenter @PathVariable , il existe donc probablement une méthode de bibliothèque que vous pouvez appeler. Étant donné que les méthodes d'interface abstraites n'ont pas de corps de méthode, ces informations de débogage n'existent pas pour les méthodes d'interface dans Java 7 et AFAIK non plus sur Java 8.
Les autres nouvelles fonctionnalités sont principalement de nouvelles API, des améliorations de HotSpot et des outils. Certaines des nouvelles API sont disponibles en tant que bibliothèques tierces (par exemple ThreeTen-Backport et streamsupport ).
En résumé, les méthodes par défaut nécessitent de nouvelles fonctionnalités JVM, mais les autres fonctionnalités linguistiques ne le sont pas. Si vous souhaitez les utiliser, vous devrez compiler le code en Java 8, puis transformer le bytecode avec Retrolambda au format Java 5/6/7. Au minimum, la version du bytecode doit être modifiée, et javac le désactive,
-source 1.8 -target 1.7
donc un rétrotranslateur est nécessaire.la source
Autant que je sache, aucun de ces changements dans JDK 8 n'a nécessité l'ajout de nouveaux bytecodes. Une partie de l'instrumentation lambda est réalisée en utilisant
invokeDynamic
(qui existe déjà dans JDK 7). Donc, du point de vue du jeu d'instructions JVM, rien ne devrait rendre la base de code incompatible. Il existe, cependant, de nombreuses améliorations associées à l'API et au compilateur qui pourraient rendre le code du JDK 8 difficile à compiler / exécuter sous les JDK précédents (mais je n'ai pas essayé cela).Peut-être que le matériel de référence suivant peut aider d'une manière ou d'une autre à enrichir la compréhension de la façon dont les changements liés à lambda sont instrumentés.
Ceux-ci expliquent en détail comment les choses sont instrumentées sous le capot. Vous pouvez peut-être y trouver la réponse à vos questions.
la source
class C extends A with B
, est mis en œuvre avec des interfaces normalesA
etB
et les classes de compagnieA$class
etB$class
. classC
transmet simplement les méthodes aux classes compagnons statiques. Les self-types ne sont pas du tout appliqués, les lambdas sont transpilés au moment de la compilation en classes internes abstraites, de même qu'unenew D with A with B
expression. La correspondance de motifs est un ensemble de structures if-else. Retours non locaux? mécanisme try-catch du lambda. Il reste quelque chose? (Fait intéressant, mon scalac dit que 1.6 est la valeur par défaut)Si vous souhaitez utiliser un «rétrotranslateur», essayez l'excellent Retrolambda d'Esko Luontola: https://github.com/orfjackal/retrolambda
la source
Vous pouvez faire
-source 1.7 -target 1.7
alors il compilera. Mais il ne se compilera pas si vous avez des fonctionnalités spécifiques à java 8 comme les lambdasla source
-source 1.7
ne volera pas.