BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Présentation Et Tutoriel Pratique Sur L'instruction "var" De Java 10

Présentation Et Tutoriel Pratique Sur L'instruction "var" De Java 10

Favoris

Points Clés

  • Java 10 introduit une fonctionnalité toute neuve: l'inférence de type pour les variables locales. Pour les variables locales, vous pouvez désormais utiliser un nom de type réservé spécial "var" au lieu du type réel.
  • Cette fonctionnalité est fournie pour améliorer le langage Java et étendre l'inférence de type aux déclarations de variables locales avec valeur d’initialisation. Cela réduit le code technique requis, tout en maintenant la vérification du type à la compilation de Java.
  • Puisque le compilateur doit inférer le type var réel en examinant le côté droit de l’expression, cette fonctionnalité présente des restrictions dans certains cas, comme lors de l'initialisation des tableaux et des Streams.
  • Expérimentez avec ce tutoriel pratique sur la façon de réduire le code technique requis en utilisant le nouveau type "var".

Dans cet article, je vais vous présenter, par l’exemple, la nouvelle fonctionnalité de Java SE 10, le type "var". Vous apprendrez comment l’utiliser correctement dans votre code et aussi quand vous ne pouvez pas l’utiliser.

Introduction

Java 10 introduit une fonctionnalité flambant neuve: l'inférence de type pour les variables locales. Pour les variables locales, vous pouvez maintenant utiliser un type réservé spécial «var» au lieu du type réel, comme le montre ce qui suit:

var name = "Mohamed Taman";

Cette fonctionnalité est fournie pour améliorer le langage Java et étendre l'inférence de type aux déclarations de variables locales avec valeurs d’initialisation. Ceci réduit le code technique requis, tout en maintenant la vérification du type à la compilation de Java.

Puisque le compilateur doit inférer le type réel du var en examinant le côté droit de l’expression, cette fonctionnalité présente des restrictions dans certains cas. Je vais en parler plus tard, continuez à lire. Passons maintenant en revue quelques exemples rapides.

Hé, attendez, attendez et attendez encore! Avant de vous lancer dans le code, vous devrez utiliser un IDE pour essayer les nouvelles fonctionnalités comme d'habitude. La bonne nouvelle est qu'il y en a beaucoup sur le marché, donc vous pouvez choisir votre IDE préféré prenant en charge Java SE 10 parmi de nombreux IDE tels qu'Apache NetBeans 9, IntelliJ IDEA 2018 ou le nouvel Eclipse.

Personnellement, je préfère toujours utiliser un outil d'environnement de développement interactif, pour apprendre rapidement la syntaxe du langage Java, explorer des nouvelles API Java et ses fonctionnalités et même pour prototyper du code complexe. Ceci remplace le cycle fastidieux d'édition, de compilation et d'exécution du code qui implique généralement le processus suivant:

  1. Rédiger un programme complet.
  2. Le compiler et corriger les erreurs éventuelles.
  3. Exécuter le programme.
  4. Déterminer ce qui ne va pas.
  5. Le modifier.
  6. Répéter le processus.

La bonne nouvelle, c'est que vous allez utiliser l'outil JShell qui est intégré et livré avec Java SE JDK depuis Java SE 9, qui était une fonctionnalité phare de cette version.

Qu'est-ce que JShell?

Java dispose désormais d’une implémentation REPL (Read-Evaluate-Print-Loop) riche avec l’outil JShell, appelée Java Shell en tant qu’environnement de développement interactif. Alors, quelle est la magie? C'est simple. JShell fournit un environnement rapide et convivial qui vous permet d'explorer, de découvrir et d'expérimenter rapidement les fonctionnalités du langage Java et ses nombreuses bibliothèques.

Avec JShell, vous pouvez entrer les éléments de programme un par un, voir immédiatement le résultat et faire les ajustements nécessaires. C'est pourquoi JShell remplace le cycle fastidieux d'édition, de compilation et d'exécution par sa boucle read-evaluate-print. Plutôt que des programmes complets, dans JShell, vous écrivez des commandes JShell et des extraits de code Java.

Lorsque vous tapez un bout de code, JShell lit, évalue et affiche immédiatement ses résultats. Ensuite, il boucle pour effectuer à nouveau ce processus pour le prochain bout de code. JShell et son feedback instantané retiennent ainsi votre attention, améliorent vos performances et accélèrent les processus d’apprentissage et de développement.

Assez d’introduction pour JShell, InfoQ a récemment publié une introduction complète à l'outil. Pour approfondir et en apprendre d'avantage sur toutes les fonctionnalités de JShell, j’ai enregistré une formation vidéo complète sur ce titre, intitulée "Hands-on Java 10 Programming with JShell[Video]" (anglais), qui devrait vous aider à maîtriser le sujet, et qui peut être suivie soit depuis Packt ou Udemy.

Voyons maintenant quelques exemples rapides pour comprendre ce qui peut être fait avec cette nouvelle fonctionnalité du type var utilisant JShell.

Logiciel requis

Pour travailler convenablement avec JShell, je suppose que Java SE ou JDK 10+ est installé et que les outils du dossier bin du JDK sont configurés pour être accessibles depuis n’importe où sur votre système, sinon voici le lien pour installer la dernière version de JDK 10+.

Démarrer une session JShell

Pour démarrer une session JShell:

  1. Microsoft Windows: ouvrez une invite de commande, tapez jshell et appuyez sur Entrée.
  2. Sous Linux, ouvrez une fenêtre shell, puis tapez jshell et appuyez sur Entrée.
  3. Sous MacOS (anciennement OS X), ouvrez une fenêtre Terminal, puis tapez la commande suivante "jshell" et appuyez sur Entrée.

Tada! Cette commande exécute une nouvelle session JShell et affiche ce message sur l’invite jshell> :

|  Welcome to JShell -- Version 10.0.1
|  For an introduction type: /help intro
jshell>

Travailler avec le type "var".

Maintenant que JDK 10 est installé, commençons à jouer avec JShell, alors passons directement au terminal pour commencer à bidouiller les fonctionnalités du type var avec des exemples. Il suffit d'entrer chacun des prochains extraits que je vous présenterai à l'invite jshell, je vous laisserai le résultat pour que vous puissiez l’étudier comme un exercice. Si vous jetez un coup d'œil rapide au code, vous remarquerez qu'il semble faux, car il n'y a pas de point-virgule. Essayez-le et voyez si ça marche ou non.

Cas d'inférence de type simple

Il s'agit de l'utilisation de base du type var. Dans l'exemple suivant, le compilateur peut inférer le coté droit de l’expression en tant que littéral String:

var name = "Mohamed Taman"
var lastName = str.substring(8)
System.out.println("Value: "+lastName +" ,and type is: "+ lastName.getClass().getTypeName())

Aucun point-virgule n'est requis car JShell est un environnement interactif. Un point-virgule n'est requis que lorsqu'il y a plusieurs instructions sur la même ligne, ou des instructions à l'intérieur d'un type déclaré ou d'une méthode, vous verrez les exemples suivants.

type var et héritage

De plus, le polymorphisme fonctionne toujours. Dans le monde de l'héritage, un sous-type de type var peut être affecté à un super-type de type var comme d’habitude comme dans les cas suivant :

import javax.swing.*
var password = new JPasswordField("Password text")
String.valueOf(password.getPassword()) // To convert password char array to string to see the value
var textField = new JTextField("Hello text")
textField = password
textField.getText()

Mais un super-type var ne peut pas être affecté à un sous-type var comme suit:

password = textField

C'est parce que JPasswordField est une sous-classe de la classe JTextField.

var et la compatibilité à la compilation

Alors maintenant, qu'en est-il d'une mauvaise affectation? La réponse est facile. Les types de variables incompatibles ne peuvent pas être assignés l’un à l’autre. Une fois que le compilateur a inféré le type réel du var, vous ne pouvez pas attribuer une valeur incorrecte comme suit:

       var number = 10
       number = "InfoQ"

Alors, que se passe-t-il ici? le compilateur vient de remplacer " var number = 10 " par " int number = 10 " pour plus de vérification, la compatibilité est toujours maintenue.

var avec les collections et les génériques

Voyons comment var fonctionne avec l'inférence des collection et des génériques. Commençons d’abord par les collections. Dans le cas suivant, le compilateur peut inférer le type des éléments de la collection:

 var list = List.of(10);

Il n’est pas nécessaire de transtyper (cast) ici, car le compilateur a inféré le type d’élément correct int

int i = list.get(0); //equivalent to: var i = list.get(0);

Dans le cas suivant la situation est différente, le compilateur le considère juste comme une collection d’objets (et non d’entiers), c’est parce que lorsque vous utilisez l’opérateur diamant, Java a déjà besoin d’un type sur le côté gauche de l’expression pour déduire le type sur le côté droit, voyons comment;

var list2 = new ArrayList<>(); list2.add(10); list2
int i = list2.get(0) //Compilation error
int i = (int) list2.get(0) //need to cast to get int back

Dans le cas des génériques, il est préférable d’utiliser un type spécifique (au lieu de l’opérateur diamant) sur le côté droit de l’expression comme suit:

var list3 = new ArrayList<Integer>(); list3.add(10); System.out.println(list3)
int i = list3.get(0)

Voyons comment le type var fonctionne à l’intérieur des différents types de boucles:

type var à l'intérieur des boucles

Voyons d'abord la boucle "for" à base d’indices

for (var x = 1; x <= 5; x++) {
           var m = x * 2; //equivalent to: int m = x * 2;
          System.out.println(m); 
}

Et voici comment c’est utilisé avec une boucle "for each"

var list = Arrays.asList(1,2,3,4,5,6,7,8,9,10)
    for (var item : list) {
          var m = item + 2;
          System.out.println(m);
}

Alors maintenant j'ai une question, est-ce que var fonctionne avec un Stream Java 8? Voyons l'exemple suivant;

   var list = List.of(1, 2, 3, 4, 5, 6, 7)
   var stream = list.stream()
   stream.filter(x ->  x % 2 == 0).forEach(System.out::println)

type var avec opérateur ternaire

Qu'en est-il de l'opérateur ternaire?

var x = 1 > 0 ? 10 : -10
int i = x 

Maintenant, que se passe-t-il si vous utilisez différents types d'opérandes sur le côté droit de l’opérateur ternaire? Voyons voir:

var x = 1 > 0 ? 10 : "Less than zero"; System.out.println(x.getClass()) //Integer
var x = 1 < 0 ? 10 : "Less than zero"; System.out.println(x.getClass()) // String

Ces deux exemples montrent-ils que le type de var est déterminé pendant l’exécution? Absolument pas! Faisons la même chose à l'ancienne:

Serializable x = 1 < 0 ? 10 : "Less than zero"; System.out.println(x.getClass())

Serializable, c'est un type commun compatible et le plus spécialisé pour les deux opérandes différents (le type le moins spécialisé serait java.lang.Object ).

String et Integer implémentent tous deux Serializable. Integer auto-boxé à partir de int. En d'autres termes, Serializable est la borne supérieure des deux opérandes. Donc, cela suggère que dans notre troisième et dernier exemple, le type var est également Serializable.

Passons à un autre sujet: passer du type var à des méthodes.

type var avec méthodes

Commençons par déclarer une méthode appelée squareOf avec un argument de type  BigDecimal, qui retourne le carré de cet argument comme suit:

BigDecimal squareOf(BigDecimal number) {
      var result= number.multiply(number);
      return result;
  }

var number = new BigDecimal("2.5")
number = squareOf(number)

Voyons maintenant comment cela fonctionne avec les génériques; déclarons de nouveau une méthode appelée toIntegerList avec un argument de type List de type T (un type générique), qui retourne une liste d'entiers de cet argument en utilisant l'API Streams comme suit:

<T extends Number> List<Integer> toIntgerList(List<T> numbers) {
               var integers = numbers.stream()
                                    .map(Number::intValue)
                                    .collect(Collectors.toList());
               return integers;
}

         var numbers = List.of(1.1, 2.2, 3.3, 4.4, 5.5)
         var integers = toIntgerList(numbers)

var avec des classes anonymes

Enfin, voyons comment utiliser var avec des classes anonymes. Profitons du threading en implémentant l'interface Runnable comme suit:

   var message = "running..." //effectively final
         var runner = new Runnable(){
                  @Override
                  public void run() {
                           System.out.println(message);
                  }}

            runner.run()

Jusqu'à présent, j'ai présenté le tout nouveau type "var" de Java 10, qui réduit le code technique requis tout en maintenant la vérification du type à la compilation de Java. Vous avez aussi parcouru des exemples qui montrent ce que l'on peut en faire. Vous allez maintenant apprendre les restrictions du type var et où il n’est pas autorisé.

Limites de "var"

Vous allez maintenant regarder quelques exemples rapides pour comprendre ce qui ne peut pas être fait avec la fonctionnalité type var. Donc, passons directement au terminal pour bidouiller les restrictions avec quelques exemples.

Les résultats de l'invite jshell expliqueront ce qui ne va pas dans le code, de sorte que vous puissiez profiter des retours instantanés interactifs

Vous devez initialiser avec une valeur

La première et la plus simple, est qu’une variable sans valeur d’initialisation n’est pas autorisée ici;

var name;

Vous obtiendrez une erreur de compilation; parce que le compilateur ne peut pas inférer le type de cette variable locale x.

La déclaration multiple n'est pas autorisée

Essayez de lancer cette ligne.

var x = 1, y = 3, z = 4

Vous obtiendrez ce message d'erreur, 'var' n’étant pas autorisé dans une déclaration multiple.

Pas de déclaration sans initialisation

Essayez de créer une méthode appelée testVar comme suit, copiez et collez simplement la méthode dans JShell:

   void testVar(boolean b) {
       var x;
       if (b) {
           x = 1;
       } else {
           x = 2;
       }
      System.out.println(x);
   }

Il ne créera pas la méthode, mais lancera une erreur de compilation. Vous ne pouvez pas utiliser 'var' sur une variable sans valeur d’initialisation. Les déclarations sans initialisation comme la présente ne fonctionnent pas pour var.

Affectation avec null

Une affectation nulle n'est pas autorisée, comme illustré ci-dessous;

var name = null;

Cela lancera une exception  "variable initializer is 'null'". Parce que null n'est pas un type.

Travailler avec des Lambdas

Un autre exemple, sans initialisation de Lambda. C’est comme dans le cas d’un opérateur diamant, le côté droit de l’expression a déjà besoin de l’inférence de type du côté gauche.

var runnable = () -> {}

Cette exception sera levée, "lambda expression needs an explicit target-type".

var et référence de méthode

Il n’y a pas d’initialisation de méthode de référence, cas similaire aux Lambdas et l’opérateur diamant:

var abs = BigDecimal::abs

Cette exception sera levée: "method reference needs an explicit target-type"

var et initialisation de tableaux

Tous les initialiseurs de tableaux ne fonctionnent pas, voyons comment var avec [] ne fonctionne pas:

var numbers[] = new int[]{2, 4, 6}

L'erreur sera:  'var' is not allowed as an element type of an array.

Ce qui suit ne fonctionne pas non plus:

var numbers = {2, 4, 6}

L'erreur est: "array initializer needs an explicit target-type"

Tout comme dans le dernier exemple, var et [] ne peuvent pas être ensemble sur le côté gauche de l’expression:

var numbers[] = {2, 4, 6}

erreur: 'var' is not allowed as an element type of an array

Seule l'initialisation de tableau suivante fonctionne:

var numbers = new int[]{2, 4, 6}
var number = numbers[1]
number = number + 3

Aucun champ var n’est autorisé

class Clazz {
  private var name;
}

Aucun paramètre de méthode var autorisé

void doAwesomeStuffHere(var salary){}

Aucun type de retour de méthode var

var getAwesomeStuff(){ return salary; }

Aucune variable dans la clause "catch"

      try {
         Files.readAllBytes(Paths.get("c:\temp\temp.txt"));
      } catch (var e) {}

Que se passe-t-il dans les coulisses pour le type var au moment de la compilation?

"var" n'est en fait que du sucre syntaxique, il n'introduit aucune nouvelle construction de bytecode dans le code compilé et pendant l'exécution, la JVM n'a aucune instruction spéciale pour eux.

Conclusion.

En conclusion de l'article, vous avez couvert ce qu’est le type "var" et comment cette fonctionnalité réduit le code technique requis, tout en maintenant la vérification du type à la compilation de Java.  

Vous avez ensuite découvert le nouvel outil JShell, l'implémentation REPL de Java, qui vous aide à apprendre rapidement le langage Java et à explorer de nouvelles API Java et ses fonctionnalités. Vous pouvez également prototyper du code complexe à l'aide de JShell, au lieu du traditionnel cycle fastidieux d'édition, de compilation et d'exécution de code.

Enfin, vous avez pris connaissance de toutes les capacités et restrictions du type var, telles que les endroits où vous pouvez et ne pouvez pas l’utiliser. C'était amusant d'écrire cet article pour vous et j'espère donc que vous l'avez aimé et trouvé utile. Si c’est le cas, faites passer le mot.

Ressources (en anglais)

  1. JDK 10 Documentation
  2. Hands-on Java 10 Programming with JShell
  3. Getting Started with Clean Code Java SE 9.
  4. Overview of JDK 10 and JRE 10 Installation
  5. JEP 286: Local-Variable Type Inference.
  6. Definite Assignment

A propos de l'auteur

Mohamed Taman is Sr. Enterprise Architect / Sr. Software Engineer @WebCentric, Belgrade, Serbie | Java Champions | Oracle Developer Champions | Membre du JCP | Auteur | EGJUG Leader | Orateur international. Tweeter Taman @_tamanm

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT