Cette section vous propose de réaliser différents programmes pour aborder les structures de contrôles.
Les concepts abordés ici sont:
if/elseswitch/caseNous allons également utiliser les tests unitaires pour tester nos programmes en utilisant la librairie JUnit4.
Pour étudier les structures de contrôles, nous allons utiliser un framework de tests unitaires : JUnit version 4
Le fonctionnement de ce framework n’est pas expliqué ici. Vous devrez suivre les modèles donnés et les adapter aux demandes faites dans ce document.
Créez un projet nommé Controle.
Allez dans les propriétés du projet.
Allez dans Java Build Path > Libraries > Add Library...
Dans la fenêtre Add library sélectionnez JUnit puis cliquez sur Next
Choisissez la version de la librairie JUnit 4.
Cliquez sur Finish puis OK
Cliquez bouton droit sur le projet et sélectionnez New > Source Folder :
test dans le champ Folder nameFinishDans chacun des répertoires sources (src et test) ajoutez un package nommé com.example.controles
Pour tout le code qui suit, le paquet à utiliser sera com.example.controles.
Deux répertoires contiendront les sources:
src (pour le code à tester)test (pour les tests)L’indication sur le répertoire (src ou test) indique que la classe sera créée dans le paquet com.example.controles du répertoire correspondant.
Pour une classe à tester, on crée la classe de test correspondante:
ControleIf sera testé dans TestControleIfControleIfElse sera testé dans TestControleIfElseEmplacement des fichiers
src
ControleIfControleIfElseControletest
TestControleIfTestControleIfElseTestControleNous allons nous doter d’une classe abstraite qui sera facilement réutilisable pour tester notre code.
Créez la classe abstraite Controle dans le répertoire src en utilisant le code source suivant:
package com.example.controles;
public abstract class Controle {
public abstract Object lancer(String[] args);
}package com.example.controles;
public class ControleIf extends Controle {
public Object lancer(String[] args) {
Object returnedValue = null;
if(args.length <= 0)
returnedValue = null;
if(args.length <= 1)
returnedValue = args[0];
if(args.length == 2)
returnedValue = args[1];
if(args.length >= 3)
returnedValue = args[2];
return returnedValue;
}
}Pour tester, on utilise une assertion. La méthode assertEquals permet de tester l’égalité de deux valeurs :
On n’écrit en général qu’une seule assertion par test (même s’il est possible d’en écrire plusieurs):
@Test
public void testControleIfUneChaine(){
assertEquals("Test une seule chaîne",
"Un", controle.lancer(new String[]{"Un"}));
}Toujours mettre @Test avant la méthode de test
Utilisation d’assertion
Pour nos travaux, uniquement une méthode qui renvoie un String
Création de l’objet controle
public class TestControleIf {
protected Controle controle ;
@Before
public void initControles(){
controle = new ControleIf();
}
//...
}@Before désigne la méthode pour initialiser
Méthode appelée avant chaque méthode de test (marquée par @Test)
Ceci permet de trouver plus facilement quelle assertion n’a pas été respectée.
Il existe d’autres méthodes que assertEquals. Voir la Javadoc de JUnit 4 qui est disponible sur cette page.
Le code source des tests est déjà écrit et est disponible ici
Dans cette partie, il vous est demandé de tester et vérifier si le fonctionnement des différents codes proposés est bien celui attendu.
Plus d’information sur les structures de contrôles est donné ici.
Créez une classe ControleIf dans le répertoire src :
package com.example.controles;
public class ControleIf extends Controle {
@Override
public Object lancer(String[] args) {
Object returnedValue = null;
if(args.length <= 0)
returnedValue = null;
if(args.length <= 1)
returnedValue = args[0];
if(args.length == 2)
returnedValue = args[1];
if(args.length >= 3)
returnedValue = args[2];
return returnedValue;
}
}Dans le répertoire src:
package com.example.controles;
public class VerifierIf {
private Controle controle ;
public static void main(String[] args) {
Controle controle = new ControleIf();
System.out.println(controle.lancer(new String[]{}));
System.out.println(controle.lancer(new String[]{"Un"}));
System.out.println(controle.lancer(new String[]{"Un", "Deux"}));
System.out.println(controle.lancer(new String[]{"Un", "Deux", "Trois"}));
}
}Créez la classe de test TestControleIf dans le répertoire test en utilisant le code source suivant:
package com.example.controles;
import org.junit.Before;
public class TestControleIf {
protected Controle controle ;
@Before
public void initControle(){
controle = new ControleIf();
}
}Ajoutez les méthodes permettant de tester dans le corps de la classe TestControleIf (voir ci-dessous).
Les imports suivants seront nécessaires:
import org.junit.Testimport static org.junit.Assert.assertEqualsUne bonne pratique consiste à ne mettre qu’une seule assertion par méthode de test.
@Test
public void testControleIfUneChaine(){
assertEquals("Test une seule chaîne",
"Un", controle.lancer(new String[]{"Un"}));
}
@Test
public void testControleIfDeuxChaine(){
assertEquals("Test deux chaînes",
"Deux", controle.lancer(new String[]{"Un", "Deux"}));
}Lancez les tests en allant dans le menu Run > Run As > JUnit Test
Ajoutez les cas pour les tableaux de chaînes suivants:
{"Un", "Deux"}{"Un", "Deux", "Trois"}{"Un", "Deux", "Trois", "Quatre"}Ajoutez la ligne suivante juste après le troisième if:
Relancez les tests. Que se passe-t-il ? Est-ce normal ?
Un bloc de code est une série d’instructions entre deux accolades {...}.
Bien qu’il soit parfaitement possible de n’utiliser qu’une seule instruction dans un if comme montré ci-dessus, il est plutôt recommandé d’utiliser des blocs de code systématiquement.
Il est parfaitement possible de ne mettre qu’une seule instruction dans un bloc, et c’est même recommandé dans les structures de contrôles:
if(args.length <= 0) {
returnedValue = null;
}
if(args.length <= 1){
returnedValue = args[0];
}
if(args.length == 2){
returnedValue = args[1];
}
if(args.length >= 3){
System.out.println("Bonjour");
returnedValue = args[2];
}
return returnedValue;Entre le code ci-dessus et la précédente version de la classe ControleIf le fonctionnement est exactement le même. La seule différence est que si on ajoute une ligne en oubliant d’ajouter des accolades (ce qui arrive fréquemment), le code continue à fonctionner correctement.
Outre l’aération et la lisibilité du code, cela le rend donc plus robuste en cas de modification.
L’instruction else s’utilise comme suit:
package com.example.controles;
public class ControleIfElse extends Controle {
@Override
public Object lancer(String[] args) {
Object returnedValue ;
if (args.length > 0 && args[0] != ""){
returnedValue = args[0];
}
else {
returnedValue = "N/A";
}
return returnedValue;
}
}Dans le répertoire src:
package com.example.controles;
public class VerifierIfElse {
private Controle controle ;
public static void main(String[] args) {
Controle controle = new ControleIfElse();
System.out.println(controle.lancer(new String[]{}));
System.out.println(controle.lancer(new String[]{"Un"}));
System.out.println(controle.lancer(new String[]{"Un", "Deux"}));
System.out.println(controle.lancer(new String[]{"Un", "Deux", "Trois"}));
}
}Créez la classe ControleIfElse et testez-la dans une méthode comme suit:
@Before
public void initControle(){
controle = new ControleIfElse();
}
@Test
public void testControleIfElse(){
assertEquals("Test une seule chaîne",
"Un", controle.lancer(new String[]{"Un"}));
}Ajoutez une instruction assertEquals permettant de vérifier l’exécution du bloc else (la valeur à tester doit être "N/A").
Testez au moins deux autres cas.
On peut critiquer le code suivant du point de vue de :
package com.example.controles;
public class ControleCascadeIfElse extends Controle {
public Object lancer(String[] args) {
String returnedValue;
int valeurChoix = Integer.parseInt(args[0]);
if(valeurChoix == 1){
returnedValue = "Menu Préférences";
}
else if (valeurChoix == 2){
returnedValue = "Menu Edition";
}
else if (valeurChoix == 3){
returnedValue = "Bonjour";
}
else if (valeurChoix == 4){
returnedValue = "Au revoir";
}
else if (valeurChoix == 5){
returnedValue = "Sortir";
}
else {
returnedValue = "Saisir votre choix (1,2,3,4 ou 5) : ";
}
return returnedValue;
}
}Dans le répertoire src, créer les deux classes suivantes:
package com.example.controles;
public abstract class VerifierDifferentsCas {
protected Controle controle ;
public abstract void initControle();
public void verifier(){
initControle();
System.out.println(controle.lancer(new String[]{"1"}));
System.out.println(controle.lancer(new String[]{"2"}));
System.out.println(controle.lancer(new String[]{"3"}));
System.out.println(controle.lancer(new String[]{"4"}));
System.out.println(controle.lancer(new String[]{"5"}));
System.out.println(controle.lancer(new String[]{"0"}));
}
}Classe de vérification:
package com.example.controles;
public class VerifierCascadeIfElse extends VerifierDifferentsCas {
@Override
public void initControle() {
controle = new ControleCascadeIfElse();
}
public static void main(String[] args) {
VerifierDifferentsCas verificateur = new VerifierCascadeIfElse();
verificateur.verifier();
}
}Ce code fonctionne et peut être testé avec les tests unitaires suivants:
package com.example.controles;
import org.junit.Test;
import org.junit.Before;
import static org.junit.Assert.assertEquals ;
public abstract class TestControleDifferentsCas {
protected Controle controle ;
@Before
public abstract void initControle();
@Test
public void testControleMenuPreferences(){
assertEquals("Test 1",
"Menu Préférences", controle.lancer(new String[]{"1"}));
}
@Test
public void testControleMenuEdition(){
assertEquals("Test 2",
"Menu Edition", controle.lancer(new String[]{"2"}));
}
/*
* COMPLETEZ LES TESTS
*/
}Créez une classe TestControleCascadeIfElse qui dérive de TestControleDifferentsCas et ajoutez y la méthode suivante :
Complétez les cas de test manquant dans le code précédent.
Voici un code équivalent avec une structure switch-case.
L’instruction break permet de stopper l’exécution du switch et d’en sortir immédiatement (vous pouvez essayer de la supprimer pour observer l’effet sur le déroulement du code).
package com.example.controles;
public class ControleSwitchCase extends Controle {
@Override
public Object lancer(String[] args) {
String returnedValue;
int valeurChoix = Integer.parseInt(args[0]);
switch(valeurChoix){
case 1:
returnedValue = "Menu Préférences";
break;
case 2:
returnedValue = "Menu Edition";
break;
case 3:
returnedValue = "Bonjour";
break;
case 4:
returnedValue = "Au revoir";
break;
case 5:
returnedValue = "Sortir";
default:
returnedValue = "Saisir votre choix (1,2,3,4 ou 5) : ";
}
return returnedValue;
}
}Retestez ce code avec les tests précédents (en créant une nouvelle classe VerifierSwitchCase dérivant de VerifierDifferentsCas et en y instanciant ControleSwitchCase) et vérifiez que son fonctionnement est correct.
Ce code fonctionne-t-il ? Que manque-t-il ?
Retestez ce code avec les tests précédents (en dérivant de TestControleDifferentsCas et en y instanciant ControleSwitchCase) et vérifiez que son fonctionnement est correct.
Ce code fonctionne-t-il ? Que manque-t-il ?
Il est possible de faire des switch-case sur des String depuis la version 7 de Java. Vous serez peut-être amenés à maintenir du code avec une version plus ancienne.
Vous ne pourrez donc pas utiliser la syntaxe suivante avec ces versions :
switch(choix){
case "1": System.out.println("Menu Préférences");
break;
case "2": System.out.println("Menu Edition");
break;
default: System.out.println("Recommencez !");
}Etudiez maintenant le fonctionnement de cette méthode :
public void testChaines(String a, String b){
if( a == null ? b == null : a.equals(b) ){
System.out.println("Les chaînes '"+ a + "' et '"+b+ "' sont les mêmes");
}
else{
System.out.println("Les chaînes '"+ a + "' et '"+b+ "' sont différentes");
}
}L’opérateur ternaire ? : qui est utilisé ici fonctionne comme suit.
Le code :
Conduit à l’affichage suivant: 2
Le code :
Conduit à l’affichage suivant: 3
Le code équivalent à (x == 1 ? 2 : 3) est :
Les deux codes suivants sont donc équivalents:
Quel est l’intérêt de cette notation ?
Quel peut être son inconvénient ?
Créez les tests unitaires permettant de vérifier le fonctionnement de la méthode testChaines(String, String)
Réécrivez cette méthode en n’utilisant que des if-else et vérifiez son fonctionnement.
Le premier mode d’utilisation de la boucle for se fait comme en langage C:
Ainsi le parcours d’un tableau se fait ainsi:
public Object lancer(String[] args) {
StringBuffer sb = new StringBuffer();
for(int i = 0 ; i < args.length; i++){
sb.append(args[i]);
}
return sb.toString();
}Une utilisation pour le parcours de listes (vues au prochain chapitre) est la suivante:
Exemple avec un StringBuffer:
public Object lancer(String[] args) {
StringBuffer sb = new StringBuffer();
for(String str : args){
sb.append(str);
}
return sb.toString();
}La structure while permet de réaliser une boucle tant-que :
public Object lancer(String[] args) {
StringBuffer sb = new StringBuffer();
int i = args.length - 1;
while(i >= 0){
sb.insert(0, args[i]);
i --;
}
return sb.toString();
}La boucle répéter-tant-que est réalisable en utilisant la structure do-while:
public Object lancer(String[] args) {
StringBuffer sb = new StringBuffer();
int i = args.length - 1;
do
{
sb.insert(0, args[i]);
i --;
} while(i >= 0);
return sb.toString();
}Testez cette boucle avec le tableau suivant:
new String[]{}Corrigez le code si nécessaire.
Cette instruction (déjà vue avec switch), permet de stopper l’exécution en cours d’une boucle.
Exemple d’utilisation de break:
public Object lancer(String[] args) {
StringBuffer sb = new StringBuffer();
for(int i = 0 ; i < args.length; i++){
if(i == 2){
break ;
}
sb.append(args[i]);
}
return sb.toString();
}L’exécution du code précédent :
new String[]{"Un", "Deux", "Trois", "Quatre"}UnDeuxCette instruction (déjà vue avec switch), permet de ne pas exécuter la suite du bloc d’instruction pour l’itération courante.
Exemple d’utilisation de continue:
public Object lancer(String[] args) {
StringBuffer sb = new StringBuffer();
for(int i = 0 ; i < args.length; i++){
if(i == 2){
continue ;
}
sb.append(args[i]);
}
return sb.toString();
}L’exécution du code précédent :
new String[]{"Un", "Deux", "Trois", "Quatre"}UnDeuxQuatreDans cette partie, nous allons étudier une fonctionnalité importante du langage Java:
EnumLes Enum sont notamment associés à la structure de contrôle switch.
Nous pouvons remplacer les éléments pour chaque case d’un switch par des constantes:
public class AvantLesEnums {
public static final String CHOIX_MENU_PREFERENCES = "1";
public static final String CHOIX_MENU_EDITION = "2";
public static final String CHOIX_SORTIR = "2";
public void appliquerChoix(String choix){
if(choix == CHOIX_SORTIR){
System.out.println("Sortie du programme");
System.exit(0);
}
switch(choix){
case CHOIX_MENU_PREFERENCES: System.out.println("Menu Préférences");
break;
case CHOIX_MENU_EDITION: System.out.println("Menu Edition");
break;
default: appliquerAutreCommande(choix);
}
}
public void appliquerAutreCommande(String choix){}
public static void main(String[] args) {
AvantLesEnums avantLesEnums = new AvantLesEnums();
avantLesEnums.appliquerChoix("2");
}
}Ceci rend le code plus lisible. Cependant, il exite un risque pour que deux constantes soient de même valeur (ici CHOIX_SORTIR est identique à CHOIX_MENU_EDITION)
Dans ce cas, le risque de dysfonctionement du programme est important, sans qu’il soit nécessairement détecté par le compilateur.
Pour palier à cela, nous pouvons utiliser les Enum.
Les Enum permettent de définir un ensemble de valeurs pour lesquels il est certain que chacune d’elles seront distinctes, tout en étant typée.
De plus, elle peuvent être utilisées dans une structure switch...case comme n’importe quelle valeur constante, à la différence qu’on est assuré qu’aucune valeur ne peut être confondue avec une autre.
Utilisation particulièrement utile dans un switch...case
On définit un Enum à la manière d’une classe, mais avec le mot clé enum:
Intérêt :
import static com.example.enumeration.ItemMenuPrincipal.*;
public class AvecLesEnums {
public void appliquerChoix(ItemMenuPrincipal choix){
if(choix == MENU_SORTIR){
System.out.println("Sortie du programme");
System.exit(0);
}
switch(choix){
case MENU_PREFERENCES: System.out.println("Menu Préférences");
break;
case MENU_EDITION: System.out.println("Menu Edition");
break;
default: appliquerAutreCommande(choix);
}
}
}On peut ajouter des méthodes de classe à un enum
public enum ItemMenuPrincipal {
MENU_PREFERENCES, MENU_EDITION, DIRE_BONJOUR, DIRE_AU_REVOIR, MENU_SORTIR;
public static ItemMenuPrincipal getItem(String identifiant){
switch (identifiant) {
case "1":
return MENU_PREFERENCES;
case "2":
return MENU_EDITION;
case "3":
return MENU_SORTIR;
}
return null;
}
}Ajoutez un main dans la classe AvecLesEnums sur le modèle du main de la classe AvantLesEnums et vérifiez ce programme.
On peut gérer les enum comme des objets, et leur ajouter des méthodes, des constructeurs:
public enum ItemMenuPrincipal {
MENU_PREFERENCES("Menu Préférences"),
MENU_EDITION("Menu Edition"),
MENU_SORTIR("Sortie du programme");
private String representation;
private ItemMenuPrincipal(String representation){
this.representation = representation;
}
public String toString(){
return representation;
}
public static ItemMenuPrincipal getItem(String identifiant){
switch (identifiant) {
case "1":
return MENU_PREFERENCES;
case "2":
return MENU_EDITION;
case "3":
return MENU_SORTIR;
}
return null;
}
}Modifiez la méthode appliquerChoix de la classe AvecLesEnums en supprimant les chaînes écrites en dur.
On peut même ajouter des comportements (nous reprenons ici les classes Calcul et dérivées :
public enum Operation {
ADDITION("+", new Addition()),
SOUSTRACTION("-", new Soustraction()),
MULTIPLICATION("x", new Multiplication());
private String representation;
private Calcul calcul ;
private Operation(String representation, Calcul calcul){
this.representation = representation;
this.calcul = calcul;
}
public int execute(int operandeA, int operandeB){
calcul.calculer(operandeA, operandeB);
return calcul.getResultat();
}
}Ce qui permet notamment de simplifier le code par ailleurs :
Attention: pour que ce code compile, il faut ajouter un import static en début de fichier.
Écrire un enum Mois permettant de récupérer :
On rappelle que :
Vous écrirez les tests unitaires permettant de tester cet enum.
Nous allons définir une classe TableauEntier permettant de stocker des entiers dans une structure dynamique.
Cette classe doit empêcher tout modification non contrôlée de ses attributs (encapsulation).
Pour cela, vous allez utiliser un tableau d’entiers (int[]) dans lequel vous devrez gérer les opérations d’ajout, de suppression et d’insertion des valeurs.
La taille initiale de ce tableau sera 5 et sera incrémentée de 5 en 5, quand le nombre d’éléments dépassera la taille disponible.
Pour cela nous allons définir les méthodes suivantes:
void inserer(int index, int valeur) : insère un entier dans le tableau en à l’index donné. Si l’index dépasse la taille du tableau, l’ajout se fait en dernière position (après avoir augmenté la taille du tableau).void supprimer(int index) : supprime l’entier situé à l’index donné en paramètre du tableau. Les autres valeurs sont alors décalées pour éviter les espaces vides.int taille() : donne la taille du tableaupublic String toString() : récupère une représentation sous forme de chaîne de caractères du tableau.
[]"[1,3,45]"Si le tableau int[] devient trop petit, vous devrez en définir un nouveau plus grand et recopier son contenu.
La classe EssaiTableauEntier comportera une méthode main(String[] args) et permettra de tester votre classe, par exemple en demandant à l’utilisateur de saisir des informations.