Logo Java 8

Java 8 – What’s new ? – 3/3 – Type Annotations et Nashorn

Java 8Nous avons vu dans les deux précédents articles 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.

Note importante : Le JDK 8 n’étant pas encore à l’état finalisé, il est possible qu’il y ait des différences entre les fonctionnalités décrites dans cet article et celles proposées dans la version finale. 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.

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.

public class GetStarted {
    public void sample() {
        @NonNull Object ref = null; // A checker will throw an exception
    }
}

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 fournit avec un framework de vérification (checking framework), il est par conséquent nécessaire d’écrire/utiliser une library (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.

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.

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 autre – 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.

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/) :

$ 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 :

$ jjs
jjs> var x = 10, y = 5
jjs> var z = x + y
jjs> z
15
jjs> print(z)
10
jjs> quit()

JavaScript depuis Java

En utilisant l’API de Scripting (JSR 223, Java 6) et le moteur JavaScript Nashorn inclut dans la JDK, nous pouvons appeler du code JavaScript très simplement. Note : Nashorn est le seul moteur fournit avec la JDK.

/*
 * org.isk.nashorn.NashornTest
 */
@Test
public void nashornString() throws ScriptException {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("nashorn");
    engine.eval("print(15 + 10)");
}

Source Pour récupérer une instance de ScriptEngine, il est aussi possible de passer “JavaScript” or “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ère et l’exécute. Dans le cas présent,

engine.eval("print(15 + 10)");

affiche “25” dans la console. Bien évidemment la méthode eval() ne sera pas utilisée avec des chaînes de caractère 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 :

/*
 * simple.js
 */
var myVariable = "jsVariable";

function sum(a, b) {
    return a + b;
}

Source

/*
 * org.isk.nashorn.NashornTest
 */
@Test
public void nashornFile() throws ScriptException, NoSuchMethodException {
    final ScriptEngineManager factory = new ScriptEngineManager();
    final ScriptEngine engine = factory.getEngineByName("nashorn");

    // Build a Reader
    final InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("simple.js");
    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    engine.eval("var mySecondeVariable = 10");
    engine.eval(inputStreamReader);

    // Get a variable from a JavaScript file
    Assert.assertEquals("jsVariable", engine.get("myVariable"));

    // Invoke a function from a JavaScript file
    final Invocable invocable = (Invocable)engine;
    final int sum = (Integer)invocable.invokeFunction("sum", 30, 20);
    Assert.assertEquals(50, sum);

    // Get a variable
    Assert.assertEquals(new Integer(10), engine.get("mySecondeVariable"));
}

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 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éfinit à 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).

Java depuis Javascript

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

$jjs 

Exemple en Java

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

/*
 * org.isk.nashorn.SimilarJavaCodeTest
 */
@Test
public void timerTask() throws InterruptedException {
    final TimerTask task = new TimerTask() {
        @Override
        public void run() {
            System.out.println("Hello!");
        }
    };

    new Timer().schedule(task, 0, 1000);
    Thread.sleep(10000);
    task.cancel();
}

Source

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.

/*
 * jjs_Packages.js
 */
var java = Packages.java;

var task = new java.util.TimerTask() {
    run: function() {
        print('Hello!');
    }
}

new java.util.Timer().schedule(task, 0, 1000);
java.lang.Thread.sleep(10000);
task.cancel();

Source

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.

/*
 * jjs_ShortPackages.js
 */
var javaPackage = Packages.java;
var TimerTask = java.util.TimerTask;
var Timer = java.util.Timer;
var Thread = java.lang.Thread;

var task = new TimerTask() {
    run: function() {
        print('Hello!');
    }
}

new Timer().schedule(task, 0, 1000);
Thread.sleep(10000);
task.cancel();

Source

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 :

with(java) {
    // Code
}

peut utiliser le nom des classes Java sans les packages.

/*
 * jjs_JavaImporter.js
 */
var java = new JavaImporter(java.lang, java.util);

with(java) {
    var task = new TimerTask() {
        run: function() {
            print('Hello!');
        }
    }

    new Timer().schedule(task, 0, 1000);
    Thread.sleep(10000);
    task.cancel();
}

Source

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.

/*
 * jjs_ShortPackagesAndClosures.js
 */
var javaPackage = Packages.java;
var TimerTask = java.util.TimerTask;
var Timer = java.util.Timer;
var Thread = java.lang.Thread;

var timer = new Timer();
timer.schedule(
    function() {
        print('Hello!');
    }, 0, 1000);

Thread.sleep(10000);
timer.cancel();

Source

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.

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 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.

Nombre de vue : 430

COMMENTAIRES 2 commentaires

  1. Nicolas Kyriazopoulos-Panagiotopoulos dit :

    Je ne vois pas trop l’animosité contre les annotations de types. C’est un alternatif aux Code Contracts proposés par Microsoft, qui remplace au final le besoin d’avoir des Tests Unitaires. Au final nous avons un code déclaratif (plus façile à maintenir et bien plus concis que les tons de tests unitaires que nous aurions besoin pour tester tout ça).
    Bien sûr, tout ça existe pour recompenser certaines problèmes des languages OO orientées du C (comme le fait que les reference types sont nullables) et qui n’existent pas dans d’autres langages comme f#.
    L’utilisation au final est une question de goût et je pense que c’est utile plutôt dans des projets ou nous avons besoin d’une vérification “formelle” de la robustesse du logiciel – par exemple les composants réutilisables.

  2. Yohan Beschi dit :

    Les points clés de la partie Annotations de types sont les suivants :
    – Aujourd’hui avons nous besoin d’utiliser un langage fortement typé pour créer une application de qualité ?
    – La solution de Java à de nombreux de ses problèmes est d’utiliser des annotations à tout va. Ceci rend le code difficilement lisible et donc difficilement maintenable. De plus, annotations et débogage font rarement bon ménage.
    – Les TDD ne seront jamais remplacés par des Annotations de type. Une pratique qui tant à se répandre est l’utilisation de langages dynamiquement typés comme Groovy pour les TU.
    – Actuellement, le nombre de développeurs lisant les rapports Sonar est assez minime. Je ne vois pas en quoi les annotations de types changeraient quelque chose à ce fait.
    – Ironiquement, on risque d’avoir du code de moindre qualité, comme par exemple la double assignation (les points sont utilisés pour formater le code correctement) :
    public void sample() {
    ..@NonNull String str = "1";

    ..if (/* ... */) {
    ....str = "2";
    ..}
    }

AJOUTER UN COMMENTAIRE