Java 10 – Le début de l’inférence de type

Ce nouvel article présente les prémices de l’inférence de type proposée avec Java 10, une fonctionnalité qui va grandement apporter à la verbosité du langage (mais également à la maintenabilité).

Qu’est ce que l’inférence de type ?

L’inférence de type est un concept en programmation, permettant au compilateur ou à l’interpréteur  de déduire automatiquement le type d’une variable en fonction du contexte.

Le diamond operator, arrivé avec Java 7, était déjà une première forme d’inférence de type appliquée aux paramètres génériques List<Integer> l = new ArrayList<>();.

Dans le cas de Java 10, l’objectif est de fournir la possibilité d’inférer les variables locales à l’aide de l’expression d’initialisation. Cette fonctionnalité est déjà disponible dans beaucoup de langages, avec des syntaxes variables : var  en C# ou en Scala, auto  en C++, def  en Groovy …

L’inférence de type en Java 10

Java 10, avec la JEPS 286, apporte donc l’inférence de type au langage Java.  var, introduit spécifiquement pour cet usage, remplace le type pour demander l’inférence du compilateur :

La syntaxe choisie est donc proche de celle qu’on peut trouver en C#. var n’est pas un mot-clef à proprement parlé mais un reserved type name, c’est à dire que l’identifieur n’est réservé que pour les noms de types, ce qui permet de laisser l’identifieur disponible pour des noms de fonctions, de variables etc… Le code suivant est donc valide :

Ce qu’il faut comprendre par là c’est que  var n’est réservé que de façon contextuelle, ce qui est un bon point en terme de rétrocompatibilité et l’usage général de nos jours : var est également un mot-clef contextuel en C# par exemple et même si auto  est un mot-clef classique en C++ (puisqu’il est directement hérité du C), des mots-clef contextuels ont été ajoutés ( override  ou final  notamment) dans les dernières normes.

De plus, comme l’indique le papier JEPS 286, il a été décidé qu’il n’y aurait pas de nouveau mot-clef introduit pour les types immuables (il faut comprendre ici les types finaux) comme le font Scala ou Kotlin en différenciant var  et val , respectivement pour les types mutables et immuables. Le spécificateur final  peut donc être ajouté à une déclaration utilisant  var comme on peut s’y attendre : final var str = "Hello";.

Notez que l’inférence va toujours choisir le type le plus spécialisé. Dans l’exemple précédent ( var list = new ArrayList<String>();), c’est ArrayList<String>  qui est inféré et non pas List<String> , Collection<String>  ou même Object . Dans les cas où vous avez besoin de travailler sur une interface précise, spécifiez simplement le type :

Cette nouvelle inférence de type pour les variables locales est la bienvenue, même si celle-ci souffre de quelques limitations (mais on peut espérer que certaines seront levées par la suite).

Les limitations de Java 10

Le JEPS 286, présente une liste de messages d’erreur liés à l’utilisation non conforme de l’inférence de type que je vous propose d’étudier un petit peu.

Il n’est pas possible d’inférer le type d’une variable sans initialiseur. Ce n’est pas très étonnant, le mieux que le compilateur pourrait faire ici serait d’inférer le type Object , communs à tous les types objet, et de faire de l’autoboxing pour les types primitifs. La décision d’interdire l’inférence de type dans ce cas est tout à fait compréhensible.

Un peu plus discutable, il n’est pas possible d’utiliser  var lorsque l’initialiseur est null  :

Inférer Object ne semble pas une idée dénuée de sens, mais à titre personnel je trouve ça assez cohérent d’avoir besoin d’un initialiseur pour inférer quoi que ce soit.

Ensuite, l’inférence de type n’est pas possible pour les tableaux :

Dans cet exemple, il est facile de voir que le type déduit devrait être int[] . Cela dit, d’autres cas plus complexes tels que var t = {'a', 28.0, 12}; ,  auraient demandé d’utiliser des règles de conversion implicite ce que Java a toujours fuit comme la peste. Mais bon, ils auraient au moins pu laisser l’inférence fonctionner lorsque tous les éléments du tableau sont du même type, comme ici, et laisser les cas complexes comme des erreurs… C’est un peu décevant mais encore une fois, l’inférence de type en est ici à sa première version et il n’est pas exclu que les choses s’améliorent par la suite.

Voyons un peu la suite :

Les lambdas Java n’étant que des implémentations déguisées d’interfaces (et ne sont pas des first class citizens), il n’est pas possible d’inférer un type sans que l’interface en question soit clairement indiquées par le programmeur. Ça fait encore une fois regretter le design des lambdas dans Java, mais l’inférence n’est pas possible sans une refonte du système. Naturellement, il en va de même pour les références de méthodes (method references) :

C’est dommage, mais on paie ici les décisions de design prises avec Java 8 et il n’y a donc pas vraiment d’étonnement à ces inférences impossibles.

Le dernier point en revanche m’a fait bondir :

Hein ?! Mais pourquoi ? Ici, l();  semble être un appel de méthode et il n’y a à priori aucune raison valable pour qu’il ne soit pas possible de déduire le type de retour d’une méthode… En plus, l’exemple donné en début du papier montre un cas qui marche bien ( var stream = list.stream(); ).

J’ai donc fait le test et je confirme que Java 10 supporte l’inférence à partir d’un appel de fonction (heureusement). J’avoue ne pas trop comprendre du coup l’idée de nous montrer ce message d’erreur.

Certaines de ces limitations sont compréhensibles et vont certainement rester comme ça ( var i; , var o = null; ), d’autres sont liées à des lacunes héritées des précédentes versions du langage (coucou les lambdas !) et d’autres (comme pour les tableaux) sont très discutables. Cela étant, Java 10 propose bel est bien une inférence de type locale qui fonctionne.

On parle bien d’inférence de type locale ici. Ne vous attendez pas à pouvoir l’utiliser par exemple en retour de fonction, comme on avait pris l’habitude en  C++ :

Pour autant, il n’est pas exclu que l’inférence de type soit étendue par la suite.

Voilà donc un petit aperçu de l’inférence de type des variables locales de Java 10. Je vais certainement faire un autre article concernant les modifications qu’apportera Java 11 à cette fonctionnalité et peut-être aussi sur les guidelines qui ont été publiées par Stuart W. Marks dans ce document pour accompagner l’ajout de cette fonctionnalité au langage. A bientôt !

 

%d bloggers like this: