I. Introduction

Nous avons vu dans les deux précédents articles (lambda et timedate) des fonctionnalités très utiles. Regardons à présent les bonnes idées pour rendre Java encore plus typé via les annotations de type et la prouesse technique de la Nashorn, le moteur JavaScript intégré dans le JDK. Non, vous ne rêvez pas, malgré son âge avancé, Java continue encore de nous surprendre.

Pour cet article, le JDK 8 build b94 a été utilisée. Le dernier build peut être téléchargé depuis jdk8.java.net. L'intégralité des sources est disponible via Github.

II. Type Annotations - JSR 308

Les Annotations de type sont probablement l'une des fonctionnalités faisant le plus débat, mais aussi qui intéresse le moins de monde et ceci alors que la JSR 308, a reçu le prix de la JSR la plus innovante en Mai 2007. Son objectif est de rendre Java - un langage fortement typé comme tout le monde le sait - encore plus typé. L'exemple suivant s'assure que la variable « ref » ne soit jamais nulle.

 
Sélectionnez
  1. public class GetStarted { 
  2.     public void sample() { 
  3.         @NonNull Object ref = null; // A checker will throw an exception 
  4.     } 
  5. } 

Avant Java 8, nous ne pouvions utiliser les annotations que sur des déclarations. A présent, elles peuvent être utilisées sur n'importe quel type. Néanmoins, la JDK n'est pas fourni avec un framework de vérification (checking framework), il est par conséquent nécessaire d'écrire/utiliser une bibliothèque (ou plusieurs pour effectuer des vérifications d'ordres différents) à ajouter dans la classpath du compilateur pour qu'il y ait une vérification. On notera que, bien qu'il s'agisse principalement d'une fonctionnalité d'audit ayant pour but de justifier de la qualité du code, elle est extrêmement intrusive et rend le code difficilement lisible.

 
Sélectionnez
  1. Map<@NonNull String, @NonEmpty List<@Readonly Document>> files; 

Alors que la tendance actuelle est de s'orienter vers plus de dynamisme, il reste quelques irréductibles du statisme et intrinsèquement de la verbosité. De plus, tant qu'il n'y aura pas d'outils permettant une intégration simple dans les modules de serveurs d'intégration continue, il sera difficile d'utiliser ce type d'annotations de manière cohérente dans une équipe de développement. Cet article étant consacré à Java 8 et non à divers frameworks, si vous souhaitez en savoir plus sur le sujet, l'Université de Washington héberge les informations relatives à la JSR 308, ainsi qu'un framework de vérification, ayant une documentation assez détaillée.

III. Moteur JavaScript Nashorn - JEP 174

Nashorn est le successeur de Rhino, le moteur JavaScript créé par Mozilla. Il est basé sur l'ECMAScript-262 et est implémenté entièrement en Java en utilisant - entre autres - invokedynamic (JSR 292), une nouvelle instruction introduite avec Java 7, notamment pour les lambdas et pour les langages dynamiques. Dans cet article, nous éviterons d'entrer dans les détails de l'implémentation pour nous concentrer sur comment appeler du JavaScript depuis du code Java et inversement. Pour connaître les détails de l'implémentation, je vous invite à consulter le code du projet qui est une véritable mine d'or.

III-A. Du JavaScript en ligne de commande

Si vous souhaitez reproduire les exemples, assurez que vous avez accès à jjs en ligne de commande (l'exécutable se trouve dans <chemin_jdk>/bin/) :

 
Sélectionnez
$ jjs -version
nashorn 1.8.0
jjs>

jjs permet d'exécuter des fichiers .js sans avoir à les appeler directement depuis du code Java (bien que ce soit ce que fait jjs). jjs est aussi un interpréteur JavaScript en ligne de commande :

 
Sélectionnez
  1. $ jjs 
  2. jjs> var x = 10, y = 5 
  3. jjs> var z = x + y 
  4. jjs> z 
  5. 15 
  6. jjs> print(z) 
  7. 10 
  8. jjs> quit() 

III-B. JavaScript depuis Java

En utilisant l'API de Scripting (JSR 223, Java 6) et le moteur JavaScript Nashorn inclus dans la JDK, nous pouvons appeler du code JavaScript très simplement.

Nashorn est le seul moteur fourni avec la JDK.

 
Sélectionnez
  1. /* 
  2.  * org.isk.nashorn.NashornTest 
  3.  */ 
  4. @Test 
  5. public void nashornString() throws ScriptException { 
  6.     final ScriptEngineManager factory = new ScriptEngineManager(); 
  7.     final ScriptEngine engine = factory.getEngineByName("nashorn"); 
  8.     engine.eval("print(15 + 10)"); 
  9. } 

Source.

Pour récupérer une instance de ScriptEngine, il est aussi possible de passer JavaScript ou ECMAScript en paramètre de la méthode getEngineByName(), à la place de nashorn. Le résultat sera identique. La méthode eval(), quant à elle, évalue la chaîne de caractères et l'exécute. Dans le cas présent,

 
Sélectionnez
engine.eval("print(15 + 10)");

affiche 25 dans la console. Bien évidemment la méthode eval() ne sera utilisée avec des chaînes de caractères que sur des cas très particuliers. La solution à privilégier est d'avoir le code JavaScript dans un fichier séparé, comme nous allons le voir :

 
Sélectionnez
  1. /* 
  2.  * simple.js 
  3.  */ 
  4. var myVariable = "jsVariable"; 
  5.   
  6. function sum(a, b) { 
  7.     return a + b; 
  8. } 

Source.

 
Sélectionnez
  1. /* 
  2.  * org.isk.nashorn.NashornTest 
  3.  */ 
  4. @Test 
  5. public void nashornFile() throws ScriptException, NoSuchMethodException { 
  6.     final ScriptEngineManager factory = new ScriptEngineManager(); 
  7.     final ScriptEngine engine = factory.getEngineByName("nashorn"); 
  8.   
  9.     // Build a Reader 
  10.     final InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("simple.js"); 
  11.     final InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 
  12.     engine.eval("var mySecondeVariable = 10"); 
  13.     engine.eval(inputStreamReader); 
  14.   
  15.     // Get a variable from a JavaScript file 
  16.     Assert.assertEquals("jsVariable", engine.get("myVariable")); 
  17.   
  18.     // Invoke a function from a JavaScript file 
  19.     final Invocable invocable = (Invocable)engine; 
  20.     final int sum = (Integer)invocable.invokeFunction("sum", 30, 20); 
  21.     Assert.assertEquals(50, sum); 
  22.   
  23.     // Get a variable 
  24.     Assert.assertEquals(new Integer(10), engine.get("mySecondeVariable")); 
  25. } 

Source.

Lignes 6 et 7 : comme dans l'exemple précédent, nous récupérons un « moteur » (engine).

Lignes 10, 11 et 13 : création d'un Reader passé à la méthode eval().

Ligne 12 : la méthode eval() prend une chaîne de caractères comme dans l'exemple précédent.

Ligne 16 : récupération d'une variable (myVariable) depuis le fichier JavaScript.

Lignes 19, 20 et 21 : exécution d'une méthode (sum) depuis le fichier JavaScript.

Ligne 24 : récupération de la variable mySecondeVariable définie à la ligne 12. Les deux appels à la méthode eval() démontrent - s'il en était besoin - qu'il y a accumulation du code JavaScript (Lignes 16, 21 et 24).

III-C. Java depuis JavaScript

Pour cette partie, il est nécessaire d'utiliser jjs et non Maven.

 
Sélectionnez
$jjs

III-C-1. Exemple en Java

Tous les exemples suivants sont équivalents au code Java ci-dessous :

 
Sélectionnez
  1. /* 
  2.  * org.isk.nashorn.SimilarJavaCodeTest 
  3.  */ 
  4. @Test 
  5. public void timerTask() throws InterruptedException { 
  6.     final TimerTask task = new TimerTask() { 
  7.         @Override 
  8.         public void run() { 
  9.             System.out.println("Hello!"); 
  10.         } 
  11.     }; 
  12.   
  13.     new Timer().schedule(task, 0, 1000); 
  14.     Thread.sleep(10000); 
  15.     task.cancel(); 
  16. } 

Source.

III-C-2. Utilisation de l'objet Packages

L'objet Packages a les propriétés java, javax et javafx, permettant de simuler l'utilisation de packages telle que nous pourrions le faire en Java.

 
Sélectionnez
  1. /* 
  2.  * jjs_Packages.js 
  3.  */ 
  4. var java = Packages.java; 
  5.   
  6. var task = new java.util.TimerTask() { 
  7.     run: function() { 
  8.         print('Hello!'); 
  9.     } 
  10. } 
  11.   
  12. new java.util.Timer().schedule(task, 0, 1000); 
  13. java.lang.Thread.sleep(10000); 
  14. task.cancel(); 

Source.

III-C-3. Simulation d'imports

Au lieu de répéter java.util et java.lang de l'exemple précédent, nous pouvons simuler l'import de packages et assigner à des variables JavaScript le chemin complet des différentes classes utilisées dans le code.

 
Sélectionnez
  1. /* 
  2.  * jjs_ShortPackages.js 
  3.  */ 
  4. var javaPackage = Packages.java; 
  5. var TimerTask = java.util.TimerTask; 
  6. var Timer = java.util.Timer; 
  7. var Thread = java.lang.Thread; 
  8.   
  9. var task = new TimerTask() { 
  10.     run: function() { 
  11.         print('Hello!'); 
  12.     } 
  13. } 
  14.   
  15. new Timer().schedule(task, 0, 1000); 
  16. Thread.sleep(10000); 
  17. task.cancel(); 

Source.

III-C-4. Utilisation de l'objet JavaImporter

Outre l'objet Packages, il y a l'objet JavaImporter qui permet d'indiquer les packages auxquels appartiennent les classes utilisées dans le code, tout comme par exemple java.util.* Tout le code inclus dans le bloc :

 
Sélectionnez
  1. with(java) { 
  2.     // Code 
  3. } 

peut utiliser le nom des classes Java sans les packages.

 
Sélectionnez
  1. /* 
  2.  * jjs_JavaImporter.js 
  3.  */ 
  4. var java = new JavaImporter(java.lang, java.util); 
  5.   
  6. with(java) { 
  7.     var task = new TimerTask() { 
  8.         run: function() { 
  9.             print('Hello!'); 
  10.         } 
  11.     } 
  12.   
  13.     new Timer().schedule(task, 0, 1000); 
  14.     Thread.sleep(10000); 
  15.     task.cancel(); 
  16. } 

Source.

III-C-5. Utilisation de fonctions anonymes

Le JavaScript permettant d'utiliser des fonctions anonymes - sans la contrainte d'interfaces fonctionnelles - nous pouvons remplacer la redéfinition de la méthode run() de la classe TimeTask - qui est une classe abstraite - par une fonction anonyme.

 
Sélectionnez
  1. /* 
  2.  * jjs_ShortPackagesAndClosures.js 
  3.  */ 
  4. var javaPackage = Packages.java; 
  5. var TimerTask = java.util.TimerTask; 
  6. var Timer = java.util.Timer; 
  7. var Thread = java.lang.Thread; 
  8.   
  9. var timer = new Timer(); 
  10. timer.schedule( 
  11.     function() { 
  12.         print('Hello!'); 
  13.     }, 0, 1000); 
  14.   
  15. Thread.sleep(10000); 
  16. timer.cancel(); 

Source.

III-D. Nashorn, une autre révolution ?

Comme mentionné dans l'introduction, faire du JavaScript côté client avec une JVM est tout de même une idée farfelue. Avec des langages tels que JRuby, Jython, etc. nous avons suffisamment de langages de scripting performants, sans avoir à utiliser du JavaScript. Laissons ceci au monde Node.js ou, si l'objectif est d'utiliser un même langage côté serveur et client, peut-être serait-il préférable de s'orienter vers des langages plus structurés tel que Dart. A noter que la team Nashorn a porté Node.js, sous le nom Node.jar. Néanmoins, Oracle ne l'a pas rendu disponible à ce jour.

IV. Conclusion

Comme nous avons pu le voir au cours de ces trois articles, Java 8 peut être considéré comme révolutionnaire. Les nouvelles fonctionnalités ne feront probablement pas revenir les développeurs s'étant dirigés vers Scala, JRuby ou Groovy, néanmoins, elles permettent de donner un nouveau souffle à un langage vieillissant. Et ceci est un point intéressant. Le C et le C++ sont bien plus anciens que Java et pourtant leur âge avancé est synonyme de qualité. On se rend donc compte que Java est rattrapé par ses manques originels. Le fait d'être fourni avec une API et la non implémentation des lambdas dans des versions antérieures - alors qu'elles existent dans de nombreux langages depuis plusieurs décennies - est un véritable handicap. Mais le problème majeur est probablement la lenteur des évolutions. Nous avons d'un côté de grosses sociétés généralement réfractaires au changement et de l'autre côté des développeurs - d'un nouveau genre - souhaitant des langages répondant à leur besoin tout de suite et non dans dix ans. Il est donc difficile de répondre au besoin de tout le monde. Et on ne peut que se réjouir des nouveautés de Java 8, en attendant la révolution de Java 9. De nombreuses entreprises ont déjà commencé leur révolution, en réduisant la part de Java, soit en le reléguant à des tâches purement backend, soit en le mixant avec d'autres langages. Et ceci n'est pas forcément une mauvaise chose, il ne fait aucun doute que les applications du futur seront des applications polyglottes.

V. Remerciements

Cet article a été publié avec l'aimable autorisation de la société SoatSoat.

Nous tenons à remercier Philippe Duval pour sa relecture attentive de cet article et Mickaël Baron pour la mise au gabarit.