Le code Java 8 peut-il être compilé pour s'exécuter sur JVM Java 7?

163

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?

Nicola Ambrosetti
la source
duplication possible de Y a-t-il des exemples spécifiques d'incompatibilités vers l'arrière entre les versions de Java?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

146

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:

$ javac Test -source 1.8 -target 1.7
javac: source release 1.8 requires target release 1.8
JesperE
la source
4
Non, je ne pense pas que ce sera le cas. Java a une petite part du marché des ordinateurs de bureau, mais conserve cette petite part dans une prise assez serrée. Mais cela entrave l'adoption de nouvelles versions et fonctionnalités. Je ne pourrai pas utiliser les fonctionnalités de Java 8 dans le code que j'écris pendant un certain temps, car je veux éviter que les gens n'aient à mettre à jour leur installation Java locale.
JesperE
Pourquoi? «Oui» signifierait que Java 8 peut être compilé pour fonctionner sur une machine virtuelle Java 7, ce qui est incorrect selon le compilateur Java 8.
JesperE
5
Maintenant, je vois: Votre «non» répond au titre de la question, pas au corps de la question.
Abdull
58

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.7donc un rétrotranslateur est nécessaire.

Esko Luontola
la source
3
En fait, les annotations de type peuvent être visibles à l'exécution. stackoverflow.com/questions/22374612/…
Antimoine
33

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.

Edwin Dalorzo
la source
7
Pas de nouveaux bytecodes, mais de nouvelles structures. Le vérificateur vomira.
Jonathan
12
Un bon exemple est celui des interfaces. Ils peuvent désormais contenir des méthodes. Le vérificateur Java7 n'est pas équipé pour gérer cela. Tous les anciens codes d'octets sont utilisés, mais d'une manière nouvelle.
Jonathan
1
Je me demande comment le compilateur scala avec autant de fonctionnalités de langage peut atteindre une version cible jvm de même jdk5.
Marinos An
1
@MarinosAn Que voulez-vous dire exactement? MI avec des traits qui contiennent des méthodes concrètes, par exemple class C extends A with B, est mis en œuvre avec des interfaces normales Aet Bet les classes de compagnie A$classet B$class. class Ctransmet 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'une new D with A with Bexpression. 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)
Adowrath
1
Bien sûr, les self-types etc. sont codés dans des attributs de classe spéciaux et des annotations afin que scalac puisse utiliser et appliquer les règles lors de l'utilisation de classes déjà compilées.
Adowrath
-5

Vous pouvez faire -source 1.7 -target 1.7alors il compilera. Mais il ne se compilera pas si vous avez des fonctionnalités spécifiques à java 8 comme les lambdas

kalgécine
la source
La question pose explicitement l'utilisation des nouvelles fonctionnalités du langage, donc -source 1.7ne volera pas.
toolforger