Le mot-clé var en Java (inférence de type de variable locale)
Utilisez var pour l'inférence de type de variable locale en Java, quand cela améliore la lisibilité et quand ce n'est pas le cas.
Depuis Java 10, vous pouvez déclarer une variable locale avec var et laisser le compilateur inférer son type à partir de l'initialiseur. var greeting = "hello"; est exactement identique, bytecode compilé compris, à String greeting = "hello"; — le type reste String, vous n'avez simplement pas eu à le répéter. C'est ce qu'on appelle l'inférence de type de variable locale : une commodité syntaxique qui supprime les noms de types redondants sans rendre Java dynamiquement typé. Bien utilisé, cela réduit le bruit ; mal utilisé, cela masque précisément les informations dont le lecteur a besoin.
Cette page explique ce que var fait et ne fait pas, exactement où il est autorisé, les cas où il est utile, les cas où il nuit à la lisibilité, et un programme exécutable qui prouve que les types inférés sont bien ceux attendus.
var est de l'inférence, pas du typage dynamique
Le fait le plus important à retenir : var n'est pas un nouveau type "fourre-tout". Le compilateur lit le membre droit, détermine le type statique et le grave dans le marbre. À partir de ce moment, la variable est aussi fortement typée que si vous aviez écrit le type à la main — vous ne pouvez pas la réaffecter à un type non lié, et le type inféré est fixé à la compilation.
var name = "Ada"; // name has static type String, forever
name = "Lovelace"; // fine, still a String
name = 42; // compile error: int cannot be assigned to Stringvar est un nom de type réservé, pas un mot-clé — vous pouvez toujours utiliser var comme nom de variable ou de méthode (même si vous ne devriez pas). Il ne déclenche l'inférence que dans la position de déclaration de variable locale.
Où var est autorisé — et où il ne l'est pas
var ne fonctionne que pour les variables locales qui ont un initialiseur. Le compilateur a besoin d'un membre droit pour lire le type ; sans initialiseur, il n'y a rien à inférer.
| Position | var autorisé ? | Raison |
|---|---|---|
| Variable locale avec initialiseur | Oui | L'initialiseur fournit le type |
Index/élément dans les boucles for | Oui | L'expression de boucle fournit le type |
| Variable de try-with-resources | Oui | L'expression de ressource fournit le type |
| Variable locale sans initialiseur | Non | Rien à inférer |
| Champs / variables d'instance | Non | L'inférence est locale par conception |
| Paramètres de méthode | Non | Les appelants, pas les initialiseurs, fournissent les valeurs |
| Types de retour de méthode | Non | Même raison que les paramètres |
Initialisé à null uniquement | Non | null n'a pas de type concret |
| Paramètres lambda (nus) | Spécial | (var x, var y) -> ... est autorisé depuis Java 11 |
var x; // error: cannot infer type, no initializer
var nothing = null; // error: null has no type to infer
public var field = 1; // error: var not allowed on fields
void m(var p) { } // error: var not allowed on parametersLe vrai bénéfice : réduire les génériques verbeux
var mérite sa place quand le nom du type est long, répété, ou chargé de génériques. Le cas classique est une déclaration où le type apparaît en entier des deux côtés du = :
// Before: the type name is written twice
Map<String, List<Customer>> byCity = new HashMap<String, List<Customer>>();
// After: the right side already says everything
var byCity = new HashMap<String, List<Customer>>();Il brille également avec les itérateurs, le Map.Entry que vous obtenez d'une HashMap, et d'autres types verbeux qui n'apportent aucune clarté quand ils sont explicitement écrits :
for (var entry : byCity.entrySet()) { // Map.Entry<String, List<Customer>>
System.out.println(entry.getKey() + " -> " + entry.getValue().size());
}Quand NE PAS utiliser var
var aide quand le type est évident à partir du membre droit et nuit quand ce n'est pas le cas. Si un lecteur doit exécuter le code mentalement pour connaître le type, écrivez le type explicitement.
var result = service.process(input); // unclear: what does process return?
Order result = service.process(input); // clear: an Order
var flag = true; // fine, obviously boolean
var count = list.size(); // fine, obviously intMéfiez-vous du piège des littéraux numériques : var infère le type du littéral, pas le type que vous aviez peut-être en tête.
var n = 100; // int, not long — for a long you must write 100L or long n
var f = 3.14; // double, not float
byte b = 1; // explicit type narrows; var b = 1 would be intÉvitez var lorsqu'il fait perdre un type d'interface délibéré. var list = new ArrayList<String>(); type list comme ArrayList<String>, pas List<String> — acceptable localement, mais si vous souhaitiez programmer selon l'interface, indiquez-le explicitement.
Un exemple concret à exécuter
Ce programme utilise var dans toutes ses positions légales — valeurs simples, une map générique, une boucle for améliorée, une boucle for indexée — et utilise getClass().getSimpleName() pour prouver que les types inférés à l'exécution sont exactement ce que les membres droits impliquaient.
Ce qu'il faut retenir de l'exécution :
greeting.getClass().getSimpleName()afficheString, prouvant quevar greeting = "hello"a produit un vraiString—varest une inférence à la compilation, et à l'exécution l'objet est exactement ce que le littéral impliquait, rien de dynamique.count + 1 = 43etprice * 2 = 19.98confirment les règles d'inférence numérique :42a fait decountunint,9.99a fait depriceundouble. Le type du littéral — pas votre intention — décide, ce qui est le piège à retenir quand vous avez besoin d'unlongou d'unfloat.scores type = HashMapmontre quevara capturé le type concret du membre droitHashMap, pas l'interfaceMap; le diamant<String, List<Integer>>à droite a fourni au compilateur tout ce dont il avait besoin même si le membre gauche ne disait quevar.total chars = 10provient defor (var name : names)oùnamea été inféré commeString, doncname.length()s'est résolu correctement (3 + 3 + 4) —varfonctionne dans les boucles for améliorées, en inférant le type d'élément à partir de l'itérable.0..4 sum = 10provient defor (var i = 0; ...)oùia été inféré commeintà partir du littéral0; la boucle indexée est l'un des endroits les plus propices à l'utilisation devarcar le type est sans ambiguïté.
Pratique
Sujets connexes
- Variables Java — déclarer et initialiser des variables locales.
- Portée des variables — pourquoi
varest intentionnellement limité à la portée locale. - Génériques — les noms de types verbeux que
varexcelle à masquer. - La boucle
for— là oùvar ietvar entryse lisent proprement.