La semaine dernière, nous avons proposé sur ce blog un quizz spécifique à Java 8. Au vu du comportement prévu pour Java 8 actuellement, la bonne réponse à ce quizz est :
x2 x2
Retrouvez toutes les explications dans la suite du billet :
- OpenJdk 8 : comportement par défaut
- Lambda, invokedynamic et metafactory
OpenJdk 8 : comportement par défaut
Pour le moment Java 8 n’est pas disponible. Seul l’OpenJDK 8 en cours de développement permet d’observer ce que sera Java 8. À l’heure où ces lignes sont écrites (nous utilisons ici l’OpenJDK 8 24.0-b07), l’OpenJDK 8 affiche par défaut :
x1 x2
En effet, par défaut, la lambda expression () -> { System.out.println(« X »); } est directement traduite par une inner classe anonyme. Pour rappel, nous avions une méthode makeX
:
private static X makeX() { return () -> { System.out.println("X"); }; }
Par défaut, elle est traduite en :
private static X makeX() { return new X() { @Override public void run() { System.out.println("X"); } } }
On peut l’observer en affichant directement x1.toString()
et x2.toString()
:
YourTestClass$1@30ae8764 YourTestClass$1@123acf34
Chaque appel à makeX
génère donc une nouvelle instance d’une classe anonyme. Si vous regardez le répertoire contenant les binaires Java (ie. *.class
), le compilateur vous a généré le fichier YourTestClass$1.class
.
Lambda, invokedynamic et metafactory
Toutefois, dans l’OpenJDK 8, le compilateur javac
propose l’option -XDlambdaToMethod
. Cette option vous permet d’activer le comportement à terme de la traduction des lambdas expressions. En activant cette option et en relançant le programme, nous obtenons la bonne réponse du Quizz. Si nous nous amusons à afficher directement x1.toString()
et x2.toString()
, nous obtenons :
YourTestClass$$Lambda$1@5506d4ea YourTestClass$$Lambda$1@5506d4ea
Chaque appel à makeX
semble fournir un singleton provenant d’une même inner classe anonyme étrangement nommée. Néanmoins, si vous regardez le répertoire de destination des binaires Java (en ayant soigneusement supprimé tous les fichiers *.class
avant d’effectuer la compilation), vous ne trouverez pas de fichier YourTestClass$$Lambda$1.class
dans vos répertoires.
Compilation
La traduction de la lambda expression n’est pas vraiment faite au moment de la compilation. En réalité, elle est répartie entre la phase de compilation et le runtime. Le compilateur va placer au niveau de la lambda expression une instruction qui a été récemment ajoutée à la JVM : la fameuse instruction invokedynamic
de la JSR292. Cette instruction est accompagnée de toutes les méta-informations nécessaires à la traduction de la lambda expression au runtime. Cela inclut le nom de la méthode à appeler, ses types d’entrée et de sortie, ainsi qu’une méthode appelée bootstrap. Le bootstrap a pour rôle de définir l’instance sur laquelle la méthode sera appelée, lorsque la JVM exécute l’instruction invokedynamic
. Dans la cadre d’une lambda expression, Java fait appel à une méthode de bootstrap particulière appelé lambda metafactory.
Pour en revenir à notre Quizz, le corps de la lambda expression est converti en méthode privée static. Ainsi, () -> { System.out.println(« X »); } est convertie dans YourTestClass en :
private static void lambda$0() { System.out.println("X"); }
Vous pouvez l’observer si vous utilisez le décompilateur javap (fournit avec le JDK) avec l’option -private. Quand à la méthode makeX
, en lieu et place de la lambda expression, vous trouverez l’instruction invokedynamic
avec notamment la référence sur un constructeur d’une classe basée sur X
.
Runtime
Lorsque vous lancez le programme, dès que la JVM tente d’interpréter l’instruction invokedynamic
pour la première fois, la JVM va faire appel à la lambda metafactory, dont nous avons parlé précédemment. Dans notre exemple, lors du premier appel à makeX
, la lambda metafactory génère une instance de X
et lie dynamiquement la méthode run
à la méthode lambda$0
. Cette instance est alors conservée en mémoire. Au second appel à makeX
, l’instance est restituée. Il s’agit par conséquent de la même instance qu’au premier appel.
Conclusion
L’utilisation d’une lambda expression dans notre exemple fait que la relation x1 == x2 est vérifiée. Tout du moins, il s’agit du comportement à terme de Java 8.
Ainsi, dans le cadre de Java 8, il peut être dangereux d’utiliser des mixins comme celui défini dans notre quizz de la semaine dernière ou comme celui que nous avons présenté dans notre article sur les méthodes virtuelles d’extension.
Références
- Rémi Forax. « JSR 292 Cookbook ». JVM Language Summit 2011.
http://wiki.jvmlangsummit.com/images/9/93/2011_Forax.pdf - Brian Goetz. « Translation of Lambda Expressions ». Avril 2012.
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html