Le Java Local Variable Type Inference (LVTI) ou, brièvement, le type var (l'identifiant var n'est pas un mot-clé, mais un nom de type réservé) a été ajouté à Java 10 à l'aide de JEP 286: Local-Variable Type Inference . Étant une fonction de compilation à 100%, elle n'affecte pas le bytecode, le runtime ou les performances. Fondamentalement, le compilateur vérifie le côté droit de l'opérateur d'affectation et, en fonction de celui-ci, détermine le type spécifique de la variable, puis le remplace par var .
De plus, il est utile pour réduire la verbosité du code passe-partout et accélère également le processus de programmation lui-même. Par exemple, il est très pratique d'écrire var evenAndOdd =...
au lieu de Map<Boolean, List<Integer>> evenAndOdd =...
L'apparition de var ne signifie pas qu'il est toujours et pratique de l'utiliser partout, parfois il sera plus pratique de le faire avec des outils standards.
Dans cet article, nous examinerons 26 situations, avec des exemples de cas oĂą vous pouvez utiliser var et quand cela n'en vaut pas la peine.
Point 1: essayez de donner des noms significatifs aux variables locales
Habituellement, nous nous concentrons sur le fait de donner les noms corrects aux champs des classes, mais nous ne prêtons pas la même attention aux noms des variables locales. Lorsque nos méthodes sont parfaitement implémentées, contiennent peu de code et ont de bons noms, alors très souvent nous ne prêtons pas attention aux variables locales, ni même réduisons complètement leurs noms.
Lorsque nous utilisons var au lieu d'écrire des types explicites, le compilateur les détecte automatiquement et substitue var . Mais d'un autre côté, à la suite de cela, il devient plus difficile pour les gens de lire et de comprendre le code, car l'utilisation de var peut compliquer sa lisibilité et sa compréhension. Dans la plupart des cas, cela est dû au fait que nous avons tendance à considérer le type d'une variable comme information principale et son nom comme secondaire. Bien que ce soit exactement le contraire.
Exemple 1:
Beaucoup conviendront probablement que dans l'exemple ci-dessous, les noms des variables locales sont trop courts:
Lorsque vous utilisez des noms courts, avec var , le code devient encore moins clair:
Option plus préférée:
Exemple 2:
Évitez de nommer des variables comme ceci:
Utilisez des noms plus significatifs:
Exemple 3:
Afin de donner des noms plus compréhensibles aux variables locales, n'allez pas aux extrêmes:
Au lieu de cela, vous pouvez utiliser une option plus courte mais non moins compréhensible:
Saviez-vous que Java a une classe interne nommée:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
Eh bien, nommer des variables avec ce type peut être délicat :)
Point 2: utilisez des littéraux pour aider à déterminer le type de primitive (int, long, float, double)
Sans l'utilisation de littéraux pour les types primitifs, nous pouvons constater que les types attendus et implicites peuvent différer. Cela est dû à la conversion de type implicite utilisée par les variables var .
Par exemple, les deux fragments de code suivants se comportent comme prévu. Ici, nous déclarons explicitement les types booléens et char :
boolean flag = true;
Maintenant, nous utilisons var , au lieu de déclarer explicitement des types:
var flag = true;
Jusqu'ici tout va bien. Faites maintenant la mĂŞme chose pour les types int , long , float et double :
int intNumber = 20;
Bien que l'extrait de code ci-dessus soit simple et direct, utilisons maintenant var au lieu de spécifier explicitement les types.
A éviter:
Les quatre variables seront sorties en tant qu'int . Pour corriger ce comportement, nous devons utiliser des littéraux Java:
Mais que se passe-t-il si nous déclarons un nombre décimal?
Évitez cela si vous prévoyez d'obtenir une variable de type float :
Pour éviter toute surprise, utilisez le littéral approprié:
Point 3: dans certains cas, les conversions de type var et implicite peuvent simplifier la prise en charge du code
Par exemple, supposons que notre code se situe entre deux méthodes. Une méthode permet d'obtenir un panier d'achat avec différents produits et de calculer le meilleur prix. Pour ce faire, il compare différents prix sur le marché et renvoie le prix total sous forme de flotteur . Une autre méthode déduit simplement ce prix de la carte.
Voyons d'abord une méthode qui calcule le meilleur prix:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
Deuxièmement, regardons la méthode qui fonctionne avec la carte:
public boolean debitCard(float amount, ...) { ... }
Maintenant, nous plaçons notre code entre ces deux méthodes de service externes en tant que client. Nos utilisateurs peuvent choisir les marchandises à acheter, nous calculons le meilleur prix pour eux, puis radions les fonds de la carte:
Après un certain temps, la société propriétaire de l'API décide d'abandonner la représentation matérielle des prix au profit de la décimale (au lieu de float , int est désormais utilisé). Ils ont donc modifié le code API comme suit:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
Le fait est que notre code utilise une déclaration explicite d'une variable flottante comme prix. Dans sa forme actuelle, nous recevrons une erreur lors de la compilation. Mais si nous avions prévu une telle situation et utilisé var au lieu de float , alors notre code continuerait à fonctionner sans problème, grâce à la conversion de type implicite:
Point 4: lorsque les littéraux ne sont pas une solution appropriée, utilisez une conversion explicite ou jetez var
Certains types primitifs en Java n'ont pas de littéraux spéciaux, par exemple des octets et des types courts . Dans ce cas, en utilisant la désignation de type explicite, nous pouvons créer des variables sans aucun problème.
Utilisez ceci au lieu de var :
Mais pourquoi dans cette situation privilégier la notation de type explicite au lieu d'utiliser simplement var ? Eh bien, écrivons ce code en utilisant var . Notez que dans les deux cas, le compilateur supposera que vous avez besoin de variables de type int .
Évitez cette erreur:
Il n'y a pas de littéraux ici qui pourraient nous être utiles, nous sommes donc obligés d'utiliser une conversion explicite de type descendant. Personnellement, j'éviterai de telles situations, car je ne vois aucun avantage ici.
N'utilisez cette entrée que si vous voulez vraiment utiliser var :
L'avantage d'utiliser var est d'écrire du code plus concis. Par exemple, dans le cas de l'utilisation de constructeurs, nous pouvons éviter d'avoir à répéter le nom de classe et, par conséquent, éliminer la redondance de code.
Évitez ce qui suit:
Utilisez plutĂ´t:
Pour la construction ci-dessous, var sera également un bon moyen de simplifier le code sans perdre d'informations.
A éviter:
Utilisez le code suivant:
Alors, pourquoi sommes-nous plus à l'aise avec var dans les exemples présentés? Parce que toutes les informations nécessaires sont contenues dans les noms des variables. Mais si var , en combinaison avec un nom de variable, réduit la clarté du code, il vaut mieux refuser de l'utiliser.
A éviter:
Utilisation:
Considérez, par exemple, l'utilisation de la classe java.nio.channels.Selector
. Cette classe a une méthode open()
statique qui renvoie un nouveau sélecteur et l'ouvre. Mais ici, vous pouvez facilement penser que la méthode Selector.open()
peut renvoyer un type booléen , selon le succès de l'ouverture d'un sélecteur existant, ou même retourner void . L'utilisation de var ici entraînera une perte d'informations et une confusion dans le code.
Point 6: le type var garantit la sécurité à la compilation
Cela signifie que nous ne pouvons pas compiler une application qui tente d'effectuer des affectations incorrectes. Par exemple, le code ci-dessous ne compile pas:
Mais celui-ci compile:
var items = 10; items = 20;
Et ce code compile avec succès:
var items = "10"; items = "10 items";
Une fois que le compilateur a défini la valeur de la variable var , nous ne pouvons assigner rien d'autre que ce type.
Point 7: var ne peut pas être utilisé pour instancier un type spécifique et l'affecter à une variable de type d'interface
En Java, nous utilisons l'approche "programmation avec interfaces". Par exemple, nous créons une instance de la classe ArrayList, en l'associant à une abstraction (interface):
List<String> products = new ArrayList<>();
Et nous évitons des choses comme lier un objet à une variable du même type:
ArrayList<String> products = new ArrayList<>();
C'est la pratique la plus courante et la plus souhaitable, car nous pouvons facilement remplacer l'implémentation d'interface par une autre. Pour cela, il suffit de déclarer une variable de type interface.
Nous ne pourrons pas suivre ce concept en utilisant des variables var, car un type spécifique leur est toujours affiché. Par exemple, dans l'extrait de code suivant, le compilateur déterminera le type de la variable en tant que ArrayList<String>
:
var productList = new ArrayList<String>();
Plusieurs arguments de défense expliquent ce comportement:
var est utilisé pour les variables locales, où, dans la plupart des cas, la programmation utilisant des interfaces est moins utilisée que dans les cas avec des paramètres de méthode renvoyés par des valeurs ou des champs
La portée des variables locales doit être petite, donc la résolution des problèmes causés par le passage à une autre implémentation ne devrait pas être très difficile
var traite le code de droite comme l'initialiseur utilisé pour déterminer le type réel. Si, à un moment donné, l'initialiseur est modifié, le type en cours de définition peut également changer, provoquant des problèmes dans le code qui repose sur cette variable.
Paragraphe 8: la probabilité d'un type inattendu
L'utilisation de var en combinaison avec un opérateur diamant (<>) en l'absence d'informations pour identifier le type peut conduire à des résultats inattendus.
Avant Java 7, l'inférence de type explicite était utilisée pour les collections:
À partir de Java 7, l' opérateur diamant a été introduit. Dans ce cas, le compilateur dérivera indépendamment le type nécessaire:
Quel type sera affiché dans le code ci-dessous?
Vous devez éviter de telles constructions:
Le type sera défini comme ArrayList<Object>
. En effet, les informations nécessaires pour déterminer correctement le type ne sont pas fournies. Cela conduit au fait que le type le plus proche sera sélectionné, ce qui peut être compatible avec le contexte de ce qui se passe. Dans ce cas, Object
.
Ainsi, var ne peut être utilisé que si nous fournissons les informations nécessaires pour déterminer le type attendu. Le type peut être spécifié directement ou passé en argument.
Spécifiez directement le type:
Passez des arguments du type requis:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
Élément 9: attribuer un tableau à une variable var ne nécessite pas de crochets []
Nous savons tous comment déclarer des tableaux en Java:
int[] numbers = new int[5];
Que diriez-vous d'utiliser var lorsque vous travaillez avec des tableaux? Dans ce cas, il n'est pas nécessaire d'utiliser des supports sur le côté gauche.
Évitez ce qui suit (cela ne compile même pas):
Utilisation:
Le code ci-dessous utilisant var ne parvient pas non plus à compiler. En effet, le compilateur ne peut pas déterminer le type du côté droit:
Élément 10: var ne peut pas être utilisé lors de la déclaration de plusieurs variables sur la même ligne
Si vous souhaitez déclarer des variables du même type à la fois, vous devez savoir que var ne convient pas pour cela. Le code suivant ne compile pas:
Utilisez plutĂ´t:
Ou est-ce:
Point 11: les variables locales doivent s'efforcer de minimiser leur portée. Le type var renforce cette déclaration.
Gardez une petite portée pour les variables locales - je suis sûr que vous avez entendu cette déclaration avant var .
La lisibilité et la résolution rapide des bogues sont des arguments en faveur de cette approche. Par exemple, définissons une pile comme suit:
Évitez ceci:
Notez que nous appelons la méthode forEach()
, héritée de java.util.Vector
. Cette méthode passera par la pile comme n'importe quel autre vecteur et c'est ce dont nous avons besoin. Mais maintenant, nous avons décidé d'utiliser ArrayDeque
au lieu de Stack
. Lorsque nous faisons cela, la méthode forEach()
recevra une implémentation de ArrayDeque qui traversera la pile comme une pile standard (LIFO)
Ce n'est pas ce que nous voulons. Il est trop difficile de suivre l'erreur ici, car le code contenant la partie forEach()
n'est pas situé à côté du code dans lequel les modifications ont été apportées. Pour augmenter la vitesse de recherche et de correction des erreurs, il est préférable d'écrire du code à l'aide de la variable de stack
, le plus près possible de la déclaration de cette variable.
Il est préférable de procéder comme suit:
Désormais, lorsque le développeur passera de Stack
Ă ArrayQueue
, il pourra rapidement remarquer l'erreur et la corriger.
Article 12: le type var simplifie l'utilisation de différents types dans les opérateurs ternaires
Nous pouvons utiliser différents types d'opérandes sur le côté droit de l'opérateur ternaire.
Lors de la spécification explicite de types, le code suivant ne se compile pas:
Néanmoins, nous pouvons le faire:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Le code ci-dessous ne compile pas non plus:
Mais vous pouvez utiliser des types plus généraux:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
Dans tous ces cas, il vaut mieux préférer var :
Il ne résulte pas de ces exemples que le type var définit les types d'objet au moment de l'exécution. Ce n'est pas le cas!
Et, bien sûr, le type var fonctionnera correctement avec les mêmes types des deux opérandes:
Point 13: le type var peut être utilisé à l'intérieur des boucles
Nous pouvons facilement remplacer la déclaration explicite des types dans les boucles for par le type var .
Changer un type int explicite en var :
Changer le type explicite d' Order
en var :
List<Order> orderList = ...;
Point 14: var fonctionne bien avec les flux dans Java 8
Il est très simple d'utiliser var de Java 10 avec des flux apparus dans Java 8.
Vous pouvez simplement remplacer la déclaration explicite de type Stream par var :
Exemple 1:
Exemple 2:
Article 15: var peut être utilisé lors de la déclaration de variables locales destinées à casser de grandes chaînes d'expressions en parties
Les expressions avec beaucoup d'imbrication semblent impressionnantes et ressemblent généralement à une sorte de code intelligent et important. Dans le cas où il est nécessaire de faciliter la lisibilité du code, il est recommandé de décomposer une grande expression à l'aide de variables locales. Mais parfois, écrire beaucoup de variables locales semble être un travail très épuisant que j'aimerais éviter.
Un exemple d'une grande expression:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Mieux décomposer le code en ses composants:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
La deuxième version du code semble plus lisible et plus simple, mais la première version a également le droit d'exister. Il est tout à fait normal que notre esprit s'adapte à la compréhension de telles expressions et les préfère aux variables locales. Cependant, l'utilisation du type var peut aider à briser de grandes structures en réduisant l'effort de déclaration des variables locales:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Article 16: var ne peut pas être utilisé comme type de retour ou comme type d'argument de méthode
Les deux extraits de code ci-dessous ne seront pas compilés.
Utilisation de var comme type de retour:
Utilisation de var comme type d'argument de méthode:
Article 17: les variables locales de type var peuvent être passées comme paramètres de la méthode ou elles peuvent prendre la valeur retournée par la méthode
Les fragments de code suivants se compileront et fonctionneront correctement:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
… Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . – :
Conclusion
« var », Java 10. , . , var , .
var Java!