BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Java : Les Fonctionnalités Manquantes

Java : Les Fonctionnalités Manquantes

Favoris

Retrouvez cet article dans notre eMag InfoQ FR consacré à Java 8.

 

Dans cet article, nous nous intéresserons aux "fonctionnalités manquantes" de Java. Avant de nous lancer complètement cependant, nous devrions noter qu'il y a certaines fonctionnalités que, délibérément, nous ne considèrerons pas. En règle générale parce qu'elles ont déjà été longuement discutées ailleurs, ou qu'elles nécessiteraient trop de travail au niveau de la VM. Par exemple :

  • Pas de génériques réifiés.

Ceci a été longuement discuté et la plupart des commentaires dénotent fréquemment d'une incompréhension de la type erasure (NdlT: "effacement de type", perte des données de types génériques lors de l'exécution). En réalité quand ils disent "je n'aime pas l'effacement de type", beaucoup de développeurs Java veulent dire "je veux List<int>". La problématique de la spécialisation des génériques sur les types primitifs n'est qu'indirectement liée à l'effacement de type, et la visibilité des génériques durant l'exécution est en vérité beaucoup moins utile que ce que la croyance populaire Java implique.

  • Arithmétique non-signée au niveau de la VM.

Le manque de support de Java en termes d'arithmétique non signée est une plainte familière des développeurs durant les jeunes années de la plate-forme, mais ceci constitue un choix de conception délibéré. Choisir de n'implémenter que l'arithmétique signée a grandement simplifié Java. Introduire des types entiers non signés maintenant représenterait un changement radical énorme pouvant entraîner de nombreux bugs subtils et difficiles à débusquer, et le risque de déstabiliser la plate-forme est juste trop grand.

  • Indices de tableaux de type long.

Encore une fois, cette fonctionnalité est simplement une modification trop profonde des arcanes de la JVM, avec un large spectre de conséquences. L'impact sur le comportement et les sémantiques des garbage collectors n'est pas des moindres. Cependant, il devrait être noté qu'Oracle s'intéresse à la question de fournir des fonctionnalités liées via un projet nommé VarHandles.

Cela vaut aussi le coup de noter que nous ne sommes pas trop préoccupés par les détails précis de la syntaxe Java pour une fonctionnalité donnée. Comme Brian Goetz l'a souvent fait remarquer à maintes occasions, les discussions autour de Java tendent à se focaliser trop sur la syntaxe, au détriment de la réflexion plus profonde sur les sémantiques qu'une fonctionnalité devrait apporter.

Maintenant que nous avons cadré les choses, nous pouvons démarrer et jeter un oeil à la première de nos fonctionnalités manquantes.

Une Syntaxe d'Import plus Expressive

La syntaxe d'import de Java est assez limitée. Les seules options disponibles sont soit d'importer une classe unique, soit un package entier. Cela mène à des imports multi-lignes lourds quand nous ne voulons que quelques classes d'un package, et nécessite des fonctions de l'IDE comme le masquage des imports pour la plupart des gros fichiers source Java.

Etendre la syntaxe d'import en autorisant de multiples classes à être importées depuis un package donnée en une seule ligne rendrait les choses un peu plus simples :

import java.util.{List, Map};

La capacité à renommer localement un type (alias) améliorerait la lisibilité et aiderait à réduire la confusion entre les types ayant le même nom de classe court :

import java.util.{Date : UDate};
import java.sql.{Date : SDate};
import java.util.concurrent.Future;
import scala.concurrent.{Future : SFuture};

Un "joker" amélioré aiderait aussi, comme ceci :

import java.util.{*Map};

Ce sont des changements de langage petits mais utiles, et ils peuvent être implémentés entièrement dans javac.

Littéraux de Collections

Java a une syntaxe (bien que limitée) pour décrire des littéraux de tableaux. Par exemple :

int[] i = {1, 2, 3};

Cette syntaxe a un certain nombre d'inconvénients, comme le pré requis que ces littéraux n'apparaissent que dans les initialisations.

Les tableaux en Java ne sont pas des collections, et les méthodes faisant le "pont" dans la classe utilitaire Arrays ont aussi des défauts majeurs. Par exemple, la méthode utilitaireArrays.asList()retourne une ArrayList, ce qui semble raisonnable jusqu'à ce qu'un examen plus proche révèle qu'il ne s'agit pas de l'usuelle ArrayList, mais plutôt d'une Arrays.ArrayList. Cette classe interne n'implémente pas les méthodes optionnelles de List et donc certaines méthodes familières déclencheront une OperationNotSupportedException. Le résultat est un raccord assez peu esthétique dans l'API qui rend le passage entre tableaux et collections inconfortable.

Il n'y a pas de raison que le langage omette une syntaxe pour déclarer des littéraux de collections. Après tout, de nombreux langages fournissent une syntaxe simple pour cela. Par exemple, en Perl on peut écrire :

my $primes = [2, 3, 5, 7, 11, 13];
my $capitals = {'UK' => 'London', 'France' => 'Paris'};

Et en Scala :

val primes = Array(2, 3, 5, 7, 11, 13);
val m = Map('UK' -> 'London', 'France' -> 'Paris');

Java, malheureusement, n'offre pas de littéraux de collections. Il en a été question de manière répétée, à la fois pour Java 7 et 8, mais cela ne s'est jamais matérialisé. Le cas des littéraux objet est aussi intéressant mais bien plus difficile à réaliser dans le cadre du système de typage de Java.

Typage Structurel

Le système de types de Java est de notoriété publique nominatif, au point d'être décrit comme "obsédé par les noms". Puisque toutes les variables doivent être d'un type nommé, il n'y a pas de possibilité d'avoir un type pouvant être exprimé uniquement via la définition de sa structure. Dans d'autres langages comme Scala, il est possible d'exprimer un type non pas en le déclarant comme une implémentation d'interface (ou d'un Trait Scala), mais plutôt en affirmant que le type doit posséder une méthode particulière. Par exemple :

def whoLetTheDucksOut(d: {def quack(): String}) {
  println(d.quack());
}

Ceci acceptera tout type possédant une méthode quack(), qu'il y ait ou non une relation d'héritage ou une interface commune entre les types.

L'utilisation de quack() comme exemple n'est pas un accident. Le typage structurel peut être relié au "typage canard" (duck typing) des langages comme Python. Mais évidemment, en Scala le typage se fait à la compilation - grâce à la flexibilité du système de types de Scala à représenter des types, difficiles voire impossibles, à exprimer en Java.

Comme initialement remarqué par James Iry, le système de types de Java autorise en fait une forme très limitée de typage structurel. Il est possible de définir un type local anonyme qui possède des méthodes additionnelles et, à condition que l'une des nouvelles méthodes soit immédiatement appelée, Java autorisera le code à compiler.

Malheureusement, la partie sympa s'arrête ici, et une unique méthode "structurelle" est tout ce que nous pouvons appeler, parce qu'il n'y a aucun type encodant l'information additionnelle que nous voulons qui puisse être retourné par notre méthode "structurelle". Comme Iry le note, les méthodes structurelles sont toutes des méthodes valides et sont présentes dans le bytecode et lors d'accès via réflexion. Elles ne peuvent juste pas être représentées par le système de types de Java. Cela ne devrait probablement pas être surprenant, puisque sous le capot, ce mécanisme est en vérité implémenté en produisant un fichier de classe supplémentaire qui correspond au type local anonyme.

Types de Données Algébriques

Les génériques de Java apportent des types paramétrés au langage, c'est à dire des références de type ayant un/des type(s) en paramètre(s). Des types concrets peuvent être alors créés en substituant un type existant au paramètre de typage. Ces types paramétrés peuvent être vus comme une composition de leurs types "contenant" (le type générique) et les types "charge utile" (la valeur des paramètres de typage).

Cependant, certains langages supportent des types qui sont composites, mais d'une manière très différente des génériques de Java (ou de la simple composition pour créer un nouveau type de donnée). Un exemple commun est le "tuple", mais un exemple plus intéressant est celui du "type de somme", parfois appelé "union disjointe de types" ou "union étiquetée".

Un type de somme est un type simplement-valué (les variables ne peuvent contenir qu'une seule valeur à la fois), mais celle-ci peut être n'importe quelle valeur valide dans un ensemble de types distincts. Cela est vrai même si les types disjoints pouvant fournir ces valeurs n'ont aucune relation d'héritage entre elles. Par exemple, dans le langage F# de Microsoft on peut définir un type Shape dont les instances peuvent être soit des rectangles ou des cercles :

type Shape =
| Circle of int
| Rectangle of int * int

F# est très différent de Java, mais plus proche de nous Scala possède une forme limitée de ces types. Les mécanismes utilisés dans les types "scellés" de Scala (sealed types) sont appliqués aux case classes. Un type scellé en Scala n'est pas extensible en dehors de l'unité de compilation courante. En Java, cela reviendrait à une classe finale, mais Scala utilise le fichier comme unité de compilation, et de multiples classes publiques peuvent être déclarées à la racine d'un unique fichier.

Cela mène à un schéma où une classe scellée abstraite est déclarée, ainsi que des sous-classes qui correspondent aux types disjoints possibles du type de somme. La librairie standard de Scala contient beaucoup d'exemples de ce pattern, dontOption[A] qui est l'équivalent Scala du type Optional<T> de Java 8.

En Scala, une Option est soit Someou None, et le type Option est une union disjointe des deux possibilités.

Si nous devions implémenter un mécanisme similaire en Java, alors la restriction que l'unité de compilation est fondamentalement la Classe rendrait cette fonctionnalité bien moins pratique qu'en Scala, mais nous pouvons malgré tout concevoir des manières de faire marcher les choses. Par exemple, nous pourrions étendre javac pour gérer une syntaxe supplémentaire sur toute classe que nous voudrions sceller :

final package example.algebraic;

Cette syntaxe indiquerait que le compilateur ne doit autoriser l'extension de la classe portant cette déclaration de package "final" qu'aux éléments présents dans le même répertoire, rejetant toute autre tentative de l'étendre autrement. Ce changement pourrait être implémenté à l'intérieur de javac, mais il ne serait évidemment pas complètement à l'abri de code utilisant la réflexion sans des vérifications durant l'exécution. Il serait aussi, malheureusement, moins pratique qu'en Scala car Java n'a pas l'expressivité de Scala.

Sites d'appel dynamiques

Avec la version 7, la plate-forme Java a ajouté une fonctionnalité qui se trouve être utile d'une manière surprenante. Le nouveau bytecode invokedynamic a été conçu pour être un mécanisme d'invocation universel.

Non seulement il permet aux langages dynamiques de s'exécuter sur la JVM, mais il permet aussi l'extension de certains aspects du système de types de Java de manières précédemment impossibles, permettant l'ajout de méthodes par défaut et de l'évolutivité des interfaces. Le prix pour cette universalité est une certaine quantité de complexité. Mais une fois compris, invokedynamic est un mécanisme puissant.

Une limitation de l'appel dynamique de méthodes est assez surprenante. Malgré l'introduction de ce support dans Java 7, le langage Java ne fournit aucun moyen d'accéder directement aux appels de méthodes. Le but du dynamic dispatch étant de permettre aux développeurs de remettre les décisions sur quelle méthode appeler depuis un site d'appel donné au moment de l'exécution, et de participer à ces décisions.

(Note : Le développeur ne devrait pas confondre ce type de lien dynamique avec le mot-clé dynamic en C#. Celui-ci introduit un objet qui résout dynamiquement ses bindings lors de l'exécution, et échouera si l'objet ne peut au final pas supporter les appels de méthodes demandés. Les instances de ces objets dynamiques sont impossibles à distinguer des objets à l'exécution, et le mécanisme est donc plutôt peu sûr.)

Bien que Java utiliseinvokedynamic sous le capot pour implémenter les expressions lambda et les méthodes par défaut, il n'y a pas d'accès direct autorisant les développeurs d'applications à effectuer un dispatch de méthode à l'exécution. Autrement dit, le langage Java n'a pas de mot-clé (ou autre) pour créer des sites d'appels universels invokedynamic. Le compilateur java n'émettra simplement pas une instruction invokedynamic hors des cas d'usages prévus dans l'infrastructure du langage.

Ajouter cette fonctionnalité au langage Java serait relativement simple. Un mot-clé, ou peut-être une annotation, serait nécessaire pour le dénoter, et cela nécessiterait un support supplémentaire en termes de librairies et de liaison des binaires.

Des lueurs d'espoir ?

L'évolution de la conception d'un langage et de son implémentation, c'est l'art du possible. Il y a beaucoup d'exemples de modifications majeures qui ont pris une éternité à être totalement adoptées entre langages. Par exemple, les expressions lambda ne sont finalement arrivées en C++ qu'avec C++14.

Le rythme de changement de Java est souvent critiqué. Mais l'un des principes guides de James Gosling était que si une fonctionnalité n'est pas assez comprise, elle ne devrait pas être implémentée. On peut argumenter que la philosophie conservatrice du design de Java a été l'une des raisons de son succès, mais cela a aussi attiré beaucoup de critiques de la part de jeunes développeurs impatients de voir des changements plus rapides du langage. Y a t'il déjà des travaux en cours qui pourraient délivrer certaines des fonctionnalités discutées ici ? La réponse pourrait être un prudent "peut être".

Le mécanisme selon lequel certaines de ces idées pourraient être réalisées a été évoqué précédemment : invoke dynamic. Rappelez-vous que l'idée sous-jacente est de fournir un mécanisme d'appel universel, remis à plus tard lors de la phase d'exécution. Une proposition récente d'amélioration, la JEP 276, offre la possibilité de standardiser une librairie nommée Dynalink. Cette librairie, créée à l'origine par Attila Szeged pendant qu'il travaillait chez Twitter, était à l'origine proposée comme une manière d'implémenter des "protocoles méta-objets" dans la JVM. Elle a été adoptée par Oracle quand Szeged les a rejoint, et utilisée largement dans les méandres de Nashorn, l'implémentation Javascript sur la JVM. JEP 276 propose maintenant de standardiser cela et de le rendre accessible à tous les langages de la JVM en tant qu'API officielle. Un aperçu de Dynalink est disponible sur Github, mais la librairie a évolué de manière significative depuis que ces sources ont été écrites.

En gros, Dynalink fournit une manière générique de parler des opérations orientées objets, comme "récupérer la valeur d'une propriété", "définir la valeur d'une propriété", "créer un nouvel objet", "appeler une méthode" sans requérir que les sémantiques de ces opérations soient remplies par les opérations bas niveau et typées statiquement qui leur correspondent dans la JVM.

Cela ouvre la porte à l'utilisation de technologies de liaison pour l'implémentation de linkers dynamiques, avec des comportements différents du linker standard Java. Cela peut être aussi utilisé pour esquisser comment des nouveautés dans le systèmes de types de Java pourraient être implémentées.

En fait, ce mécanisme a déjà été évalué par certains développeurs du coeur de Scala comme possible remplaçant de l'implémentation des types structurels. L'implémentation actuelle est forcée de s'appuyer sur la réflexion, mais l'arrivée de Dynalink pourrait changer tout ça.

A propos de l'Auteur

Ben Evans est le CEO de jClarity, une startup d'analyse de la performance de Java/la JVM. Dans son temps libre, il est l'un des leaders de la Communauté Java Londonienne et tient un siège au Comité Exécutif du JCP (Java Community Process). Ses précédents projets incluent le test de performance de l'IPO Google, des systèmes de trading financiers, la réalisation de sites web récompensés pour certains des plus gros films des années 90, entre autres.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT