• 15 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 28/01/2019

Utilisez les tableaux pour organiser vos données

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

En regardant de plus près notre code, on s'aperçoit que les 3 questions posées à notre utilisateur (menu, accompagnement, boisson) ont toujours le même fonctionnement :

  • un affichage propose les choix ;

  • on récupère la saisie de notre utilisateur ;

  • on switch pour afficher les réponses ;

  • on recommence avec une boucle, si la réponse n'est pas la bonne.

Dans ce chapitre, on va créer une méthode unique qui va servir pour toutes nos questions. Pour faire cela nous allons utiliser les tableaux. Si vous voulez une introduction aux tableaux, vous pouvez lire la partie 2 de ce cours sur l'algorithmique.

Pour récupérer une version propre du projet prêt pour ce chapitre, vous pouvez importer le projet git suivant : MyMenu - Step 6

Les tableaux

Imaginez que vous deviez sauvegarder tous les âges des élèves d'une classe. Si la classe compte 27 élèves, il va vous falloir 27 variables de type  int  pour contenir ces 27 valeurs.

Et bien non ! Pour faire face à ce type de problématique, on trouve dans les langages de programmation un type de variable appelé tableaux !

En Java, il est symbolisé par 2 crochets[]. La syntaxe est un peu particulière :

type[] nom_de_la_variable = new type[taille du tableau]

On a :

  • le type des informations contenu dans le tableau (int, double, String, etc..) ;

  • 2 crochets, ouvrant [ et fermant ] ;

  • le nom de votre variable de type tableau ;

  • le symbole = ;

  • le mot-clé  new , signifiant que l'on va créer un nouveau tableau ;

  • de nouveau le type ;

  • enfin, entre crochets[] la taille du tableau.

Essayons directement avec JShell :

jshell> int[] ages = new int[27];
ages ==> int[27] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  ... , 0, 0, 0, 0, 0, 0, 0, 0 }

Jshell nous confirme bien que nous venons de créer un tableau de 27 cases. Il nous affiche les premières et dernières valeurs du tableau. Par défaut il s'agit de  0 .

Pour accéder aux valeurs, on fait :

jshell> ages[5];
$23 ==> 0

jshell> ages[5] = 22;
$24 ==> 22

jshell> ages[5];
$25 ==> 22

jshell> ages[7] = ages[5] + 5;
$26 ==> 27

jshell> ages[0] = ages[0] * 2;
$27 ==> 0

jshell> ages[27];
|  java.lang.ArrayIndexOutOfBoundsException thrown: 27
|        at (#31:1)

On peut même faire des boucles sur nos tableaux !

shell> for (int counter = 0; counter < 27; counter++) {
   ...> ages[counter] = 5;
   ...> }

jshell> ages
ages ==> int[27] { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5} 

Plutôt pratique non ?

Il existe un dernier moyen de créer un tableau quand on connait déjà les valeurs de ce tableau :

jshell> int[] ages = {27, 42, 18, 31, 84};
ages ==> int[5] { 27, 42, 18, 31, 84 } 

Notre méthode à tout faire

Voici la signature de notre nouvelle méthode servant à afficher n'importe quelle question :

public void askSomething(String category, String[] responses)

Cette fonction prend en paramètre :

  • la catégorie de la question (menu, boisson ou accompagnement) ;

  • un tableau contenant les réponses possibles pour la question.

Comme on l'a dit plus haut, cette fonction va :

  • afficher la question et les réponses possibles à l'utilisateur ;

  • récupérer la saisie de notre utilisateur ;

  • afficher ce que l'utilisateur a choisi ;

  • faire une boucle pour recommencer si la réponse n'est pas bonne.

Voici un test associé à cette méthode :

@Test
public void Given_BadResponseAndResponse1_When_AskAboutCarWithThreeResponses_Then_DisplayErrorAndGoodResponse() {
    System.setIn(new ByteArrayInputStream("5\n1\n".getBytes()));
    order = new Order();
    String[] responses = {"BMW", "Audi", "Mercedes"};
    order.askSomething("voiture", responses);
    String[] output = outContent.toString().replace("\r\n", "\n").split("\n");
    assertEquals(true, output[0].contains("voiture"));
    assertEquals("Vous n'avez pas choisi de voiture parmi les choix proposés", output[5]);
    assertEquals("Vous avez choisi comme voiture : BMW", output[6]);
}

Pourquoi ce test utilise des voitures ? Notre méthode doit fonctionner avec n'importe quelle question. Donc avec des voitures aussi ! 

Voilà ce que donne la méthode  askSomething  :

/**
 * Display a question about a category in the standard input, get response and display it
 * @param category the category of the question
 * @param responses available responses
 */
public void askSomething(String category, String[] responses) {
    System.out.println("Choix " + category);
    for (int i = 1; i <= responses.length; i++) {
        System.out.println(i + " - " + responses[i - 1]);
    }
    System.out.println("Que souhaitez vous comme " + category + "?");
    int nbResponse;
    boolean responseIsGood;
    do {
        nbResponse =sc.nextInt();
        if (nbResponse >= 1 && nbResponse <= responses.length) {
            responseIsGood = true;
        } else {
            responseIsGood = false;
        }
        if (responseIsGood == true) {
            System.out.println("Vous avez choisi comme " + category + " : " + responses[nbResponse - 1]);
        } else {
            System.out.println("Vous n'avez pas choisi de " + category + " parmi les choix proposés");
        }
    } while (responseIsGood == false);
}

Si on s'arrête là, on a un souci. Notre méthode fonctionne parfaitement pour une boisson, une voiture ou un musicien. Mais que va t-il se passer avec un accompagnement ? On aura la phrase :

Vous n'avez pas choisi de accompagnement parmi les choix proposés

C'est souvent le risque quand on mutualise du code, ça fonctionne presque mais pas parfaitement. On va donc ajouter une condition pour savoir si notre catégorie commence ou non par une voyelle.

/**
 * Display a question about a category in the standard input, get response and display it
 * @param category the category of the question
 * @param responses available responses
 */
public void askSomething(String category, String[] responses) {
    System.out.println("Choix " + category);
    for (int i = 1; i <= responses.length; i++) {
        System.out.println(i + " - " + responses[i - 1]);
    }
    System.out.println("Que souhaitez vous comme " + category + "?");
    int nbResponse;
    boolean responseIsGood;
    do {
        nbResponse = sc.nextInt();
        if (nbResponse >= 1 && nbResponse <= responses.length) {
            responseIsGood = true;
        } else {
            responseIsGood = false;
        }
        if (responseIsGood == true) {
            System.out.println("Vous avez choisi comme " + category + " : " + responses[nbResponse - 1]);
        } else {
            boolean isVowel = "aeiouy".contains(Character.toString(category.charAt(0)));
            if (isVowel) {
                System.out.println("Vous n'avez pas choisi d'" + category + " parmi les choix proposés");
            } else {
                System.out.println("Vous n'avez pas choisi de " + category + " parmi les choix proposés");
            }
        }
    } while (responseIsGood == false);
}

On va maintenant pouvoir créer 3 méthodes  askMenu ,  askSide  et  askDrink . Comme d'habitude voici des tests :

@Test
public void Given_Chiken_When_AskAboutMenus_Then_DisplayChikenChoice() {
    System.setIn(new ByteArrayInputStream("1\n".getBytes()));
    order = new Order();
    order.askMenu();
    String[] output = outContent.toString().replace("\r\n", "\n").split("\n");
    assertEquals("Vous avez choisi comme menu : poulet", output[5]);
}
@Test
public void Given_FriesWithAllSidesEnabled_When_AskAboutSides_Then_DisplayFriesChoice() {
    System.setIn(new ByteArrayInputStream("2\n".getBytes()));
    order = new Order();
    order.askSide(true);
    String[] output = outContent.toString().replace("\r\n", "\n").split("\n");
    assertEquals("Vous avez choisi comme accompagnement : frites", output[5]);
}
@Test
public void Given_Water_When_AskAboutDrinks_Then_DisplayWaterChoice() {
    System.setIn(new ByteArrayInputStream("1\n".getBytes()));
    order = new Order();
    order.askDrink();
    String[] output = outContent.toString().replace("\r\n", "\n").split("\n");
    assertEquals("Vous avez choisi comme boisson : eau plate", output[5]);
}

Voici les 3 méthodes :

/**
 * Display a question about menu in the standard input, get response and display it
 */
public void askMenu() {
    String[] menus = {"poulet", "boeuf", "végétarien"};
    askSomething("menu", menus);
}

/**
 * Display a question about side in the standard input, get response and display it
 */
public void askSide(boolean allSidesEnable) {
    if (allSidesEnable) {
        String[] responsesAllSide = {"légumes frais", "frites", "riz"};
            askSomething("accompagnement", responsesAllSide);
    } else {
        String[] responsesOnlyRice = {"riz", "pas de riz"};
        askSomething("accompagnement", responsesOnlyRice);
    }
}

/**
 * Display a question about drink in the standard input, get response and display it
 */
public void askDrink() {
    String[] responsesDrink = {"eau plate", "eau gazeuse", "soda"};
    askSomething("boisson", responsesDrink);
}

Vous pouvez vérifier que tout est bon en lançant les tests.

On va maintenant utiliser  askSide  et  askDrink  directement dans  runMenu .

public void runMenu() {
    this.displayAvailableMenu();
    int nbMenu;
    do {
        nbMenu = sc.nextInt();
        this.displaySelectedMenu(nbMenu);
        switch (nbMenu) {
            case 1:
                askSide(true);
                askDrink();
                break;
            case 2:
                askSide(true);
                break;
            case 3:
                askSide(false);
                askDrink();
                break;
        }
    } while (nbMenu < 1 || nbMenu > 3);
}

Lancez les tests ! Ils sont toujours verts, c'est normal. Nous n'avons pas changé le comportement du programme.

 Et pour  askMenu  ?

Nous ne pouvons pas encore l'utiliser. Il ne sera pas capable d'appeler  askSide  et  askDrink  en fonction des situations. Nous verrons dans le prochain chapitre comment faire.

Comme d'habitude voici notre UML :

UML avec askSomething
UML avec askSomething

Un peu d'optimisation

On va maintenant se pencher sur la méthode  askSomething . Actuellement, elle ferait trembler plus d'un développeur !

Voici une optimisation :

public void askSomething(String category, String[] responses) {
        System.out.println("Choix " + category);
        for (int i = 1; i <= responses.length; i++)
            System.out.println(i + " - " + responses[i - 1]);
        System.out.println("Que souhaitez-vous comme " + category + "?");
        int nbResponse;
        boolean responseIsGood;
        do {
            nbResponse = sc.nextInt();
            responseIsGood = (nbResponse >= 1 && nbResponse <= responses.length);
            if (responseIsGood)
                System.out.println("Vous avez choisi comme " + category + " : " + responses[nbResponse - 1]);
            else {
                boolean isVowel = "aeiouy".contains(Character.toString(category.charAt(0)));
                if (isVowel)
                    System.out.println("Vous n'avez pas choisi d'" + category + " parmi les choix proposés");
                else
                    System.out.println("Vous n'avez pas choisi de " + category + " parmi les choix proposés");
            }
        } while (!responseIsGood);
    }

Voyons voir ce que l'on a fait :

Pour le  for  et le  if , on a retiré les accolades {}. Quand le code à exécuter à l'intérieur d'une boucle ou d'une condition ne fait qu'une ligne. On peut supprimer les accolades.

responseIsGood = (nbResponse >= 1 && nbResponse <= responses.length);

Je vous l'ai déjà expliqué, une condition va en fait créer une variable de type  boolean. Plutôt que d'avoir une condition  if/else  qui mettra  true  ou  false  dans la variable  responseIsGood . On peut faire comme ci-dessus.

if (responseIsGood)

De la même façon si  responseIsGood  vaut déjà  true , pas besoin de le comparer avec  true .

while (!responseIsGood);

Ici c'est un peu plus technique. Un point d'exclamation ! devant une variable de type  boolean  va transformer cette variable en son opposé (true  devient  false  et  false  devient  true ).

Avec ce  do/while , on continue la boucle tant que  responseIsGood  est  false .

static

Pour l'instant  askSomething  n'est utile que dans  Order . Si l'application évolue et permet de gérer de nombreuses situations on aura peut-être besoin d'accéder à cette fonctionnalité depuis plusieurs classes. Je vais vous montrer comment faire pour l'exemple sans modifier réellement notre code.

Pour faire cela, on peut utiliser le mot-clé  static  et déplacer la fonction dans une classe dédiée  Interaction . 

package com.cursan.anthony;

public class Interaction {
    public static void askSomething(String category, String[] responses) {
        System.out.println("Choix " + category);
        for (int i = 1; i <= responses.length; i++)
            System.out.println(i + " - " + responses[i - 1]);
        System.out.println("Que souhaitez-vous comme " + category + "?");
        int nbResponse;
        boolean responseIsGood;
        do {
            nbResponse = sc.nextInt();
            responseIsGood = (nbResponse >= 1 && nbResponse <= responses.length);
            if (responseIsGood)
                System.out.println("Vous avez choisi comme " + category + " : " + responses[nbResponse - 1]);
            else {
                boolean isVowel = "aeiouy".contains(Character.toString(category.charAt(0)));
                if (isVowel)
                    System.out.println("Vous n'avez pas choisi d'" + category + " parmi les choix proposés");
                else
                    System.out.println("Vous n'avez pas choisi de " + category + " parmi les choix proposés");
            }
        } while (!responseIsGood);
    }
}

En faisant ça, on peut désormais utiliser cette méthode n'importe où dans notre code en faisant :

Interaction.askSomething(question, responses);

Sans devoir utiliser le mot-clé  new .

Java intègre différentes classes qui sont codées avec ce fonctionnementMath  en est le meilleur exemple. Cette classe permet d'accéder à de nombreuses fonctionnalités mathématiques via des méthodes static.  

jshell> Math.tan(10);
$3 ==> 0.6483608274590866

jshell> Math.sin(5);
$4 ==> -0.9589242746631385

jshell> Math.floor(5.45);
$5 ==> 5.0

jshell> Math.pow(3, 2)
$6 ==> 9.0

Concrètement, ça change quoi, à l'utilisation, ce static ?

  1. Si une méthode est définie avec static, l'appel s'effectue directement sur laclasse : par exemple, dans  Math.tan(10), tan est une méthode static de la classe Math

  2. Si la méthode n'est pas static, l'appel s'effectue sur l'objet : par exemple dans  category.charAt(0)  , category est un objet de type String et charAt une méthode non static de la classe String

On peut utiliser exactement le même fonctionnement avec un attribut.

package com.cursan.anthony;

public class Interaction {
    public static int VALUE = 18;
    
    ...
}

Math  intègre par exemple la valeur de  PI .

jshell> Math.PI
$1 ==> 3.141592653589793

Exemple de certificat de réussite
Exemple de certificat de réussite