Publié le 13/06/2018 Par Gaëtan Eleouet

Java 10 est sorti il y a quelques semaines et la nouveauté la plus visible pour le développeur est l’introduction du mot clé var, lié à l’inférence de type des variables locales.
Découvrons ce qui se cache derrière cette notion, comment l’utiliser et surtout quand ne pas l’utiliser !

J’utilise pour cet article openJDK-10.0.1 et le shell interactif jshell en mode verbose pour avoir la description des types inférés.

L’introduction du mot clé var est décrite dans la JEP 286. Son intérêt, pour reprendre les mots de l’auteur (Brian Goetz) est “d’améliorer l’expérience de développement en réduisant le cérémonial autour de l’écriture de code Java” ( En anglais dans le texte : « improve the developer experience by reducing the ceremony associated with writing Java code »)
Ce n’est pas un mot clé du langage, mais un nom de type réservé, c’est à dire que l’on ne peut plus appeler une classe var, c’était de toute manière une façon bien maladroite de nommer une classe, mais on peut toujours appeler une variable var.
Pas de let. Java reste typé statiquement, l’inférence de type est faite à la compilation, le bytecode généré est exactement le même que lorsque le type est explicité, ce qui assure qu’il n’y a pas d’impact sur la performance.
Pas de val, const, etc. non plus, les variables locales ne sont pas le meilleur endroit pour définir des constantes et Java possède déjà la notion de variable effectivement finale depuis Java 8. Il faut donc écrire final var pour expliciter une variable finale.

La syntaxe

jshell> var i = 0
i ==> 0
|  created variable i : int

Remarque : le shell interactif ne nécessite pas de ; à la fin des lignes.

On a ainsi créé une variable locale i de type entier initialisée à 0. La valeur d’initialisation est obligatoire, c’est elle qui permet l’inférence de type.

jshell> var i
|  Error:
|  cannot infer type for local variable i
|    (cannot use 'var' on variable without initializer)
|  var i;
|  ^----^

La variable déclarée peut ensuite être lu ou modifiée classiquement.

jshell> i++
…

jshell> i
i ==> 1
|  value of i : int

Utilisation

On peut utiliser le mot clé var seulement pour des variables locales (pas pour les arguments d’une méthode, ni son type de retour, ni un attribut de classe).
On peut donc les utiliser dans une boucle for.

jshell> for (var i = 0; i < 5; i++) System.out.println("Bonjour! " + i)
Bonjour! 0
Bonjour! 1
Bonjour! 2
Bonjour! 3
Bonjour! 4

Plus intéressant avec les collections :

jshell> var annuaire = new HashMap<String, String>()
annuaire ==> {}
|  created variable annuaire : HashMap<String,String>


jshell> annuaire.put("Jean", "0123456789")
…

jshell> annuaire.put("Jacques", "0987654321")
…


jshell> for (var e : annuaire.entrySet()) System.out.println(e.getKey() + " -> " + e.getValue())
Jacques -> 0987654321
Jean -> 0123456789

Enfin un avantage, plus besoin de déclarer la variable d’itération comme un Entry<String, String> !!

On remarque également que le type d’annuaire est HashMap, celui de l’implémentation réelle fournie comme valeur d’initialisation.

jshell> var t = new TreeMap<Integer, String>()
t ==> {}
|  created variable t : TreeMap<Integer,String>

jshell> t.put(5, "Cinq")
…
jshell> t.put(3, "Trois")
…
jshell> System.out.println("First key " + t.firstKey())
First key 3

On a bien accès aux méthodes de la classe TreeSet. Le type inféré par le mot clé var est celui de l’implémentation.

Utilisation avec une factory

Le cas sans doute le plus intéressant.

Avant :

Map<String, List<String>> friendsPreferredMovies = retrieveBestMoviesOf(bestFriends);

Après :

var friendsPreferredMovies = retrieveBestMoviesOf(bestFriends);

L’écriture est effectivement simplifiée, attention toutefois à ne l’utiliser que lorsque l’information de type perdue n’est pas importante.

On pourrait objecter également qu’utiliser une map de liste n’est pas une bonne idée… une telle utilisation doit être justifiée, primitive obsession smell.

var avancé

Que peut-on faire de plus avec var ?

jshell> var p = new Runnable() {
  ...> int count = 0;
  ...> public void run() {
  ...> for (int i = 0; i < 10; i++) count++;
  ...> }}
p ==> $1@1bce4f0a
|  created variable p : <anonymous class implementing Runnable>

Nous déclarons une instance p d’une classe anonyme.

jshell> p.run()

jshell> System.out.println(p.count)
10

L’inférence de type, assigne le type réel à la variable p, ce qui nous permet d’accéder aux attributs déclarés dans la classe anonyme !
Cette nouvelle possibilité ouvre de nombreuses perspectives, au niveau de l’écriture d’un code potentiellement plus clair, ce n’est toutefois pas une révolution.

var et les collections

jshell> var l = List.of(1, 2)
l ==> [1, 2]
|  created variable l : List<Integer>

Ici on remarque que le type du constructeur est List, ce qui est le type inféré grâce au type de retour de la factory of. Sans surprise, on a une List<Integer>.

jshell> var m = List.of(1, 2.)
m ==> [1, 2.0]
|  created variable m : List<Number&Comparable<? extends Number&Comparable<?>>>

L’intérêt est ici évident, jamais je n’aurais envie d’écrire ce type manuellement. On peut être surpris par le Number&Comparable<...> mais en Java, Number n’implémente pas Comparable, le compareTo étant implémenté dans chaque classe fille.

Pour une réponse plus complète, voir la mutabilité des AtomicXXX et ce lien.

jshell> var n = List.of(1, "Un")
n ==> [1, Un]
|  created variable n : List<Serializable&Comparable<? extends Serializable&Comparable<?>>>

Un autre exemple qui montre le fonctionnement de l’inférence de type.

var VS 💎 (opérateur diamant)

L’information n’est ni sur var, ni sur le <>, le compilateur ne fait pas de miracle.

jshell> var a = new ArrayList<>()
a ==> []
|  created variable a : ArrayList<Object>

Le type inféré est Object 🙁

var et les lambdas

En un mot, NON.
Les lambdas ne peuvent être déclarées par le mot clé var, le type d’une lambda dépendant du type de la variable à laquelle on l’assigne.

jshell> var l = () -> 1
|  Error:
|  cannot infer type for local variable l
|    (lambda expression needs an explicit target-type)
|  var l = () -> 1;
|  ^--------------^

La réponse du compilateur est explicite !

Conclusion

Le mot clé var n’est pas une révolution, mais il permet tout comme l’opérateur <> d’améliorer la lisibilité de certains codes.
Il faut par contre se montrer encore plus vigilant au nommage et à la portée des variables lorsqu’on l’utilisera.
Ce mot clé n’est pas magique et ne fonctionne pas avec les lambdas: il faut choisir entre var et l’opérateur diamant lors d’une utilisation avec les collections.
Enfin le type inféré est celui de l’implémentation lors de la création via new et demande un soin particulier pour ne dépendre que de l’interface (même dans le corps d’une méthode, dépendre d’une implémentation est une mauvaise pratique).
Permettre l’accès aux attributs d’une classe anonyme est un bonus, l’usage n’en est toutefois pas certain.

Vos commentaires

  1. Par CARREAU, le 15 Juin 2018

    Sympa ! Ça fait maintenant quelques années que j’ai « quitté » le dev Java et ça fait plaisir de voir un tel mot clé implémenté ! A savoir si dans l’utilisation ça ne rend effectivement pas le code « moins lisible » à force de l’utiliser partout 🙂

Publier un commentaire

Gaetan Eleouet 2024

Auteur

Gaëtan Eleouet

Gaëtan est un développeur passionné, il s’intéresse particulièrement à tout ce qui a trait à l’écosystème Java, à l’intelligence artificielle et aux pratiques de développement.
Il a commencé sur des interfaces graphiques en Java, dans l’industrie puis en développement rapide en salle de marché. Il a ensuite consolidé les aspects professionnels du développement dans des grands groupes en finance et est maintenant également enseignant en école d’ingénieur.