Code Java : | Sélectionner tout |
String message = "Salut le monde !";
Code Java : | Sélectionner tout |
var message = "Salut le monde !";
Derrière cette nouvelle syntaxe se cache le JEP 286 - Local-Variable Type Inference (inférence du type des variables locale) destiné à simplifier la vie du développeur ou, selon les mots de son créateur : rendre l’écriture du code Java moins cérémoniale (c'est-à-dire moins "lourdingue"). Ainsi désormais ces 3 lignes sont à peu près équivalentes :
Code Java : | Sélectionner tout |
1 2 3 4 5 | List<String> list = new Arraylist<String>(); // JDK 5. List<String> list = new Arraylist<>(); // JDK 7. var list = new Arraylist<String>(); // JDK 10. |
Dans tous les cas, le compilateur saura retrouver le bon type de la variable et acceptera qu'on puisse appliquer les méthodes de l'interface List<String> sur cette variable.
Cependant comme toute nouvelle fonctionnalité, il ne faut pas en abuser quand ce n'est pas nécessaire sous risque de rendre le code illisible, incompréhensible et surtout difficile à maintenir par un autre développeur ou même pour soi. L'OpenJDK vient donc de publier un guide pour l'usage du mot-clé var. Ce guide repose sur quelques principes fondamentaux :
- Il est plus important de pouvoir lire du code que de l’écrire - être concis c'est bien, mais pas aux dépens de la relecture ou de la compréhension ;
- Le code ne doit pas être dépendant d'une réflexion locale - s'il faut regarder à plusieurs endroits pour arriver à comprendre ce que fait une variable, c'est qu'il y a un souci ;
- On ne doit pas se reposer sur son IDE - le code peut être amené à être lu avec des outils divers (autres IDE ou même simple éditeur de texte) ; on ne doit pas se reposer sur un IDE en particulier pour savoir ce que fait cette variable. Utiliser l'IDE pour explorer la variable est déjà en soi une distraction dans l’exercice de programmation ;
- Le typage explicite est un compromis - déclarer un type local alourdit certes le code, mais il permet d'un autre côté la compréhension claire et immédiate de ce que ce code effectue.
En se reposant sur ces principes, l'auteur avance donc les règles d'utilisation suivantes :
1. Choisir un nom de variable qui fournit une information
C'est une règle de programmation basique, mais elle devient encore plus nécessaire lorsqu'on utilise le mot-clé var : le code devient tout de suite beaucoup plus parlant si l'information qui auparavant était fournie par le typage est désormais contenue dans le nom de la variable.
2. Minimiser la portée des variables locales
À nouveau une règle de bonne conduite assez basique, mais souvent peu respectée : ici, il ne s'agit pas simplement d'optimisation ou de gestion de la mémoire, mais tout simplement du fait que si une variable déclarée en var est utilisée dans un énorme bloc de code, il y a de fortes chances pour que vous ayez oublié quel est son type lorsque vous êtes enfin arrivé sur la ligne en question et que vous êtes amené à l'utiliser. Vous allez donc devoir repartir en arrière jusqu’à sa déclaration ou son affectation et essayer de vous souvenir quel est son type concret. Si vous êtes amené à faire des modifications sur le type de la variable, vous allez devoir repasser tout le bloc au peigne fin pour savoir quel impact cela a de changer le type.
3. Utiliser var lorsque l'initialisation donne suffisamment d'information au lecteur
La plupart du temps les variables locales sont initialisées en invoquant des constructeurs avec l’opérateur new ou encore des méthodes de fabrique. Dans ces deux cas l'information sur le type est on ne peut plus claire et donc il est tout à fait approprié d'utiliser var.
4. Utiliser var pour découper des expressions chaînées ou encastrées avec des variables locales
L'introduction des flux de données (data streams) dans le JDK 8 a permis de saisir du code concis et performant, mais qui peut se révéler obtus, difficile à relire et dont on peut avoir du mal à saisir toutes les subtilités lorsqu'on enchaîne trop de commandes les unes derrière les autres. Lorsque trop de commandes s’enchaînent, il convient donc de découper sa chaîne en plusieurs parties ; l'utilisation de var permettant de simplifier le typage de chacune des sous-parties en rendant le code plus léger que si on avait dû écrire tous les types comme auparavant.
5. C'est une variable locale, pas besoin de se casser la tête avec l'interface de base !
Ici il est bon de rappeler que var ne peut être utilisé que sur les variables locales et aucunement sur les membres d'instances ou statiques ni même sur les paramètres ou le type de retour des méthodes. Le mot-clé var n'a donc aucun impact dans ces secteurs pour lesquels la règle reste d'utiliser le type le plus généraliste quand cela est possible.
Évidemment classiquement, et comme c'est recommandé, on a tendance à vouloir utiliser les super-interfaces plutôt que les classes concrètes pour le typage des variables locales. Cependant lorsque le type est inféré c'est automatiquement le type concret qui est choisi par le compilateur. Du coup, nous pouvons utiliser toutes les méthodes du type concret ce qui peut former une dépendance dure envers ce type. Si jamais nous sommes amenés à changer le type utilisé pour initialiser la variable, le compilateur générera immédiatement des erreurs en cas d’incompatibilité. Si nous avons respecté la règle de la portée courte, l'impact sera minimal et facile à corriger.
6. Attention à l'utilisation du diamant et des génériques !
Évitez de mélanger var et l’opérateur diamant <> lorsque vous utilisez des génériques ! Ces deux syntaxes sont destinées à alléger l’écriture du code, mais pas à être utilisées ensemble. Le fait de ne plus avoir de type explicite à droite comme à gauche de l'expression d'initialisation va faire que le type le plus général (à savoir Object) va être utilisé.
De manière similaire, l'utilisation de certaines méthodes de fabrique génériques peut être dangereuse lorsque le manque d'argument ne permet pas au compilateur d’inférer le type. Par exemple List.of() : il n'y a aucun souci lorsque la méthode de fabrique dispose d'au moins un argument ; dans ce cas le compilateur pourra inférer le type. Par contre lorsqu'elle n'en a pas, le compilateur ne pourra pas inférer le type et c'est donc le type Object qui sera utilisé.
7. Attention à l'utilisation des littéraux !
Il est tout à fait possible d'utiliser des valeurs littérales pour initialiser des variables déclarées en tant que var. Cela ne pose pas de souci pour des valeurs de type boolean, char, long ou String du fait de leur syntaxe explicite de déclaration (ex. : true, 'a', 10L, "Salut").
Par contre, il faudra faire extrêmement attention aux variables de type byte, short et long qui sont initialisées via des valeurs littérales en notation entière qu'elles soient octales, décimales ou hexadécimales (ex. : 0158, 17, 0x00ff) ; en effet, le compilateur peut être amené à changer le type de votre variable pour les inférer en int !
Pour les nombres flottants, de manière générale, les types sont inférés correctement du fait de la déclaration distincte des valeurs qui sont explicites (ex. : 3.0f, 5.5d). Mais il arrive que dans du code on puisse initialiser une variable locale de type double initialisée à partir d'un champ de type float ; si on utilise var à la place de double alors cette variable sera inférée en type float !
Source : Style Guidelines for Local Variable Type Inference in Java par Stuart W. Marks