• 10 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 15/04/2024

Introduction du cours

Qu’est-ce qu’un programme informatique ? Comment peut-on dire à un ordinateur de dire bonjour, de compter jusqu’à cent, de jongler, d’envoyer des fusées dans l’espace ?

Il suffit de lui demander… Et c’est le rôle du développeur !

En suivant ce cours, vous allez apprendre les bases d’un des langages de programmation les plus populaires : le  Java ! 

En commençant par les bases, et en pratiquant avec des exercices, vous allez découvrir comment développer des programmes répondant à des besoins réels.

Nous irons ensuite ensemble dans l’univers de la programmation orientée objet, là où Java montre toute sa puissance.

Pour finir, nous verrons des éléments pour aller plus loin, mieux contrôler vos programmes, les bonnes pratiques…

Ne perdons pas plus de temps, allons découvrir les possibilités qu’offre la programmation en Java ! Rejoignez-moi dans le premier chapitre !

Gérez les variables de votre programme en Java

Tirez le maximum de ce cours

Bonjour et bienvenue dans ce cours !

Connaissez-vous le principe d’un cours en ligne sur OpenClassrooms ?

Ce cours suit une progression logique que l’on a séquencée en 3 parties. Chaque partie contient plusieurs chapitres, qu’il est préférable de suivre dans l’ordre.

Dans ces chapitres, vous retrouverez régulièrement :

  • des screencasts tutoriels. Ce sont des vidéos de démonstration qui permettent de suivre étape par étape la réalisation d’un point du cours directement sur l’ordinateur de l’expert-formateur ;

  • des exercices dans les sections "À vous de jouer". C’est l’occasion de mettre en pratique ; ils sont précieux pour accélérer votre apprentissage !

Et à la fin de chaque partie du cours, vous trouverez :

  • un quiz pour vous permettre de valider ce que vous avez appris

Mettez en pratique avec des exercices interactifs

Dans ce cours, vous mettrez ce que vous avez appris en pratique dans des exercices de code interactifs sur Replit. Pour accéder aux exercices, suivez les instructions ci-dessous :

  1. créez un compte ou connectez-vous à votre compte Replit (c’est gratuit),

  2. cliquez sur le lien suivant pour accéder aux exercices de ce cours : Apprenez à programmer en Java,

  3. utilisez les liens dans les sections “À vous de jouer” pour retrouver l’exercice de chaque chapitre. Attention, vous repasserez par la page d'accueil et devrez choisir le bon exercice en fonction du cours et chapitre sur lequel vous êtes positionné.

Si vous voulez retrouvez ces exercices plus tard sur Replit, vous pouvez aller dans vos équipes (“Teams” dans le menu à gauche), puis cliquez sur le nom de ce cours sous la section “Education”.

Profitez de chaque occasion de pratiquer en faisant une pause dans le cours pour vous entraîner de votre côté, et reproduire pas à pas ce que vous voyez dans les vidéos de mise en pratique : c’est la clé de la réussite !

Êtes-vous prêt à découvrir le monde de Java ? Allez, c’est parti !

Rencontrez votre professeur

Raphaël Bertin a mis à jour le cours ! Il vous accompagnera tout au long de votre apprentissage au travers des vidéos de démonstration.

Raphaël est développeur Android et mentor OpenClassrooms. Sa passion : la création d'applications mobiles et d’interfaces utilisateurs soignées. Il est toujours en quête des meilleures pratiques de programmation.

Téléchargez la fiche résumé du cours

 

Déclarez des variables

Nous y voilà, vous allez enfin découvrir comment programmer une voiture autonome, à moins que vous ne vouliez concevoir un robot pour faire la cuisine ?

Bon, il va tout de même falloir commencer par les bases. Cela tombe bien, nous y sommes. Et nous allons tout de suite voir les notions indispensables ! Commençons par les variables !

Qu’est-ce qu’une variable ?

En quoi est-ce si important ?

Eh bien, concrètement, un programme est constamment en train de manipuler des variables, certaines qu’on lui donne, d’autres qu’il crée de lui-même.

On peut comparer une variable à une boîte dans laquelle est rangée une information. Cette boîte est stockée sur une étagère dans un entrepôt gigantesque. L'emplacement de chaque boîte de cet entrepôt est soigneusement enregistré, et géré par l’ordinateur.

Pour savoir à quoi sert chaque boîte, vous devez l’étiqueter. Avec la programmation, c'est la même chose : vous attribuez un nom à votre variable.

Nommez une variable

Voici quelques recommandations générales pour la création de noms :

Utilisez des noms descriptifs tout au long de votre code

Ça risque d'être un peu long ! Cependant, les noms descriptifs sont bien pratiques à long terme pour vous et pour votre équipe, car ils offrent une meilleure lisibilité et facilitent la compréhension du code pour les autres développeurs. Par exemple, si vous voulez stocker des cookies sans sucre, l'utilisation d'un nom descriptif comme  cookiesSansSucre  est bien plus précis que, disons,  cookies  ou  cookiesSains.

Soyez complet

Évitez si possible d'abréger ou de raccourcir les mots, même si une version plus courte semble évidente. Par exemple,  chiffreDAffairesAnnuel  est préférable à   chifAfAnn  .

Suivez une convention d'appellation commune

L'une des conventions d'appellation les plus populaires est le Camel Case : une phrase composée de plusieurs mots sans espaces ni ponctuation. Le premier mot est écrit en minuscules et tous les autres mots commencent par une majuscule. Par exemple,  monBudget.

Déclarez une variable

Pour utiliser une variable dans votre code, vous devez la créer, ou, en langage de développeur, la déclarer. On annonce qu’elle existe.

En Java, les variables qui contiennent des nombres entiers sont déclarées en utilisant un mot clé tel que  int  suivi du nom d'une variable. On dit que ce sont des ints.

Ensuite, il faut ranger une valeur dans cette variable. Par exemple :

int incomes = 500;
int savings = 1000;

Mais qu’est-ce que ce “;” ?

Ici, nous avons déclaré deux variables :   incomes  et    savings  . Ces variables stockent respectivement les valeurs de 500 et 1 000.

Modifiez la valeur d'une variable avec les opérateurs

Une variable peut varier, c'est-à-dire changer de valeur. Elle porte bien son nom, n'est-ce pas ? 

Pour la faire varier, vous pouvez effectuer plusieurs opérations.

Reprenons les variables   incomes  et   savings  de l'exemple précédent. Vous pouvez :

  • ajouter de l'argent au montant épargné ;

  • soustraire de l'argent aux dépenses en cours ;

  • découvrir combien de temps il vous faudra pour atteindre 5 000 euros si vous épargnez 500 euros par mois ;

  • découvrir le montant de vos dépenses si vous continuez à ajouter 30 euros par jour pendant une semaine ;

  • découvrir le montant de vos dépenses si vous dépensez 10 euros par jour pendant une semaine.

Des problèmes du monde réel avec des solutions de programmation !

Eh oui, chaque opération fonctionne grâce à des opérateurs arithmétiques :

  • +  addition ;

  • -  soustraction ;

  • *  multiplication ;

  • /  division.

Tout comme en mathématiques, vous pouvez utiliser des parenthèses pour décider de ce qui se passe, et quand.

Voyons comment vous pouvez atteindre votre objectif en Java :

Regardez ce joli bloc de code :

public class ManipulationVariables {
   
   public static void main(String[] args) {
      int epargne = 500;
      int revenus = 2000;
      //Ajoutez 100 à votre épargne (Yeah!)
      epargne = epargne + 100;

      //Enlevez 50 à votre indemnité (Snif)
      revenus = revenus - 50;
      //Faites une mise à jour sur votre délai d'épargne
      int nombreDeJoursEpargne = (5000 - revenus) / 500;
    
      //Mettez à jour à nouveau votre indemnité (encore)
      revenus = revenus + (30 - 10) * 7;
   }
}

Vous remarquerez que :

  • toutes les lignes ne se ressemblent pas ;

  • les lignes commencent par  //  : ce sont des commentaires qui permettent aux autres personnes de mieux comprendre le code. Le programme sait qu’il ne doit pas prendre en compte cette conversation entre développeurs !

Ici, chaque affectation assigne une valeur à une variable.

Est-ce qu'on peut résumer ?

Oui ! Pour affecter une valeur à une variable, vous écrivez une affectation. Cette affectation se compose du nom de la variable, suivie de l'opérateur d'affectation, et enfin de l'expression qui produit une valeur correspondant au type de la variable.

Écrivez un code plus court avec des opérateurs d'affectation raccourcis

Chaque affectation attribue une valeur à une variable. Vous pouvez directement assigner une valeur à droite de l'opérateur d'affectation. Voici un exemple :

//remplacez la variable épargne par le nouveau montant
epargne = 10000;

D'ailleurs, lorsque vous avez besoin de changer la valeur d'une variable avec des opérateurs de base et de l'affecter à cette variable, vous pouvez utiliser une version raccourcie ! Voici un exemple. Au lieu d'utiliser   epargne + 100  et l'opérateur d'affectation   =  , vous pouvez utiliser un opérateur d'affectation joint à l'opérateur arithmétique   +=  :

// Version d'affectation normale
epargne = epargne + 100;
// Version raccourcie d'affectation
epargne += 100;

Les autres variantes courtes sont :

  • +=  addition ;

  • -=  soustraction ;

  • *=   multiplication ;

  • /=  division.

Manipulez d’autres données que des nombres

Dans l'exemple que nous avons utilisé, toutes les variables stockent des montants d'argent, qui sont des nombres entiers.

Quels autres types de valeurs pouvons-nous affecter aux variables ?

Vous avez vu qu’une variable était définie par un nom et une valeur. Pour pouvoir stocker différents contenus dans les boîtes (ou bocaux), vous devez définir le type de la variable.

Supposons que vous soyez en train de travailler sur une application d'écriture, et que vous ayez besoin d'analyser du texte et de calculer ce qui suit :

  • le nombre de voyelles présentes dans le texte ;

  • le pourcentage de voyelles.

Vous pouvez décomposer le processus comme suit :

  1. Demandez du texte à l'utilisateur. 

  2. Parcourez le texte fourni par l'utilisateur caractère par caractère. 

  3. Augmentez votre total à chaque fois que vous trouvez une voyelle. 

  4. Divisez le nombre final de voyelles par le nombre total de caractères de la chaîne pour obtenir le pourcentage.

  5. Multipliez ce résultat par 100 pour le pourcentage final. 

De combien de variables auriez-vous besoin pour y parvenir ? Pensez au nombre d'informations individuelles que vous devez stocker :

  1. La séquence initiale de caractères que vous demandez à l'utilisateur (une chaîne de caractères).

  2. Le nombre de voyelles présentes dans la chaîne.

  3. Le pourcentage de voyelles.

On dirait que vous avez besoin de trois variables ! Pour définir chacune d'entre elles, vous avez besoin des composants suivants :

  1. Un type, qui définit le type de variable que vous avez : chaîne (texte), entier (nombre entier), ou décimal (virgule flottante).

  2. Une valeur initiale, qui vous donnera un point de départ.

Vous en saurez bientôt plus sur les types de variables !

  • String  = texte ;

  • int  = nombre entier ;

  • double  = nombre en virgule flottante.

En Java, vous pouvez déclarer vos trois variables comme ceci :

String text = "A wonderful string that consists of multiple characters";
int numberOfVowels = 0;
double percentageOfVowels = 0.0;

Découvrez des variables qui ne changent jamais

Jusqu'à présent, la plupart des valeurs changeaient en fonction des circonstances (ajouter de l'argent aux économies, augmenter le nombre de voyelles). Certaines valeurs, cependant, n'ont pas besoin d'être modifiées. Elles restent exactement telles qu'elles ont été définies au départ.

L'utilisation de constantes est utile pour deux raisons :

  1. Elles permettent aux programmes d'être plus rapides. L'ordinateur sait combien d'espace une constante prend. Cela signifie que lorsqu'il effectue des opérations, il n'a pas besoin de vérifier les valeurs alternatives.

  2. S’assurer que certaines valeurs ne changent pas, que ce soit intentionnellement ou par accident. Par exemple, vous ne voudriez pas modifier les jours de la semaine ou le nombre de jours dans une année. 

Déclarons quelques constantes et voyons comment elles fonctionnent.

Pour déclarer une constante en Java, vous devez utiliser le mot clé final :

final int NUMBEROFWEEKDAYS = 7;
final String MYFAVOURITEFOOD = "Icecream";
int numberOfPets = 1;
String currentSeason = "Winter";

Il y a des variables et des constantes dans l'exemple ci-dessus. Si vous essayez de modifier les valeurs de toutes ces variables, seules les variables passeront, et les constantes généreront des erreurs :

NUMBEROFWEEKDAYS = UMBEROFWEEKDAYS + 1; // Error
MYFAVOURITEFOOD = "Cake"; // Error
numberOfPets = 3; // Ok
currentSeason = "Summer"; // Ok

Si vous regardez le fonctionnement des variables dans différents langages de programmation, vous allez probablement remarquer quelques différences. Il est important de les connaître lorsque vous commencez à programmer dans un environnement spécifique. Cependant, vous observerez aussi beaucoup de similitudes. Bien que les variables puissent sembler différentes d'un langage à l'autre, les concepts restent les mêmes. Ne l'oubliez pas si vous décidez de commencer à programmer dans un autre langage.

À vous de jouer !

Prêt à coder ? Pour accéder à l’exercice, suivez ce lien.

En résumé

  • Une déclaration de variable est composée de trois éléments : type, nom, et valeur.

  • Les valeurs des variables peuvent être modifiées.

  • Les variables à valeurs constantes sont appelées constantes.

  • Le nom des variables doit respecter les conventions de dénomination courantes.

Dans le chapitre suivant, nous allons nous intéresser aux types de variables.

Choisissez le bon type de variable

Spécifiez le type de variable

La seule façon de déclarer une variable en Java est de spécifier directement son type : on parle de typage fort.

Regardons la déclaration suivante de la variable   count  :

Pour déclarer une variable, il faut commencer par le renseigner le type (ici int), puis le nom de la variable, puis le signe égal (=), puis sa valeur, sans oublier le point-virgule à la fin.
Déclaration d'une variable

Par exemple, si vous voulez compter des mots dans une phrase, mais que vous ne savez pas encore de quelle phrase il s'agit, indiquez le type, et déclarez la variable pour une utilisation future.

int nombreDeMotsDansLaPhrase;

Si je ne peux pas utiliser une variable sans valeur, pourquoi donc j'ai besoin de spécifier le type ? Pourquoi pas uniquement la valeur ?

Pendant l'exécution d'un programme, votre processeur (unité centrale de traitement) a besoin de savoir combien d'espace il va devoir réserver à votre variable. Utiliser un type pour déclarer une variable permet à votre processeur de lui allouer un espace mémoire adapté.

Parmi les types de données, il existe les types primitifs. Ce sont des types qui existent par eux-mêmes, comme des atomes. Nous en avons déjà vu certains : int et double.

Les types les plus simples servent de base à toutes les opérations informatiques.

Parmi les types primitifs, vous serez souvent amené à utiliser les types numériques et les chaînes. Parlons-en un peu !

Découvrez les types numériques

Les types numériques sont :

  • les nombres entiers, comme les nombres que vous utilisez pour compter (1, 2, 3) ;

  • les nombres décimaux, que vous pouvez utiliser pour stocker les valeurs monétaires (2,50 ou 5,99, par exemple).

Les nombres entiers

Commençons par un type que vous connaissez déjà bien : les entiers ! Les entiers sont déclarés comme n'importe quelle autre variable, avec un type, puis un nom, puis, si vous l'avez, une valeur :

int count = 10;

Vous reconnaissez le mot clé   int  du dernier chapitre ?

C'est parce que vous déclarez des entiers avec le mot clé type   int  . Votre variable   count  a un type entier. Et sa valeur est de 10, qui est... un nombre entier !

Les nombres décimaux

Pour les nombres décimaux, Java utilise deux types différents :

  • float;

  • double.

Ces deux types ont le même but. La différence est que   double  est deux fois plus précis que   float  , ce qui signifie qu'il propose plus de décimales après la virgule.

Si vous avez besoin de stocker quelque chose de grand, comme un numéro à 12 chiffres :   1876.79797657  , vous devrez utiliser   double  . En effet,   float  ne pourra stocker que la moitié de ce chiffre... Le reste serait tronqué et perdu à tout jamais !

Ces types sont déclarés et initialisés de la façon suivante :

float length = 1876.79;
double width = 1876.79797657;

Mais que se passe-t-il si je fournis des valeurs supérieures ?

Par exemple :

float a = 1876.79797657765609870978709780987;
double b = 1876.79797657765609870978709780987;

Eh bien, elles vont perdre toutes les deux un peu de précision, mais pas au même degré :

// a -> 1876.79
// b -> 1876.797976

Avec des valeurs plus petites, cela ne fait aucune différence :

float length = 10.0;
double width = 10.0;

Dans l'exemple ci-dessus, les deux variables contiennent de petites valeurs. Sur la deuxième ligne, nous utilisons simplement   double  au lieu de   float  . Comme cela ne fait aucune différence fonctionnelle, je vous encourage à utiliser la plus petite dans cet exemple, puisque l'utilisation d'une plus grande boîte pour stocker les petites valeurs gaspille de la mémoire !

Mélangez des types numériques

Dans vos programmes, vous pouvez être amené à faire des opérations mathématiques. Cependant, les variables utilisées ne seront pas forcément de même type (tant qu'elles restent des valeurs numériques). C'est pourquoi il est important de garder à l'esprit la façon dont les types se mélangent, et les conséquences que cela peut avoir. Par exemple, si vous divisez deux nombres :   5/2  , on pourrait s’attendre logiquement à un résultat de   2,5  . Mais pour l'ordinateur, ce n'est pas si évident que ça, et vous pourriez obtenir   2  de façon assez inattendue !

Voyons ça en vidéo :

Découvrez les booléens

En langage Java, pour valider une condition, vous utilisez un type de données spécifique appelé   boolean  . Une variable de type booléen ne peut contenir que deux valeurs : true ou false. Un   boolean  est le type de données le plus simple.

Voici comment déclarer un boolean en Java :

boolean isCodingInJava = false;

Comme pour les types numériques que vous venez de voir, nous pouvons changer la valeur de notre variable :

isCodingInJava = true; // Nous changeons la valeur à true

Lorsque vous utilisez des booléens, il est très probable que vous ayez besoin de simplement inverser sa valeur, peu importe sa valeur actuelle. En Java, vous pouvez utiliser le NON logique :

boolean isCodingInJava = true;
isCodingInJava = !isCodingInJava; // Inversion de la valeur, donc false
isCodingInJava = !isCodingInJava; // Inversion de la valeur, donc true

Utilisez le type  String  pour les chaînes de caractères

Passons maintenant à un type plus complexe :  String  .

En vérité, le  String  est un objet ! N’ayez pas peur, c’est le sujet de la deuxième partie de ce cours. Vous avez pour l’instant tout ce qu’il vous faut pour comprendre la suite.

Voici comment déclarer une variable String dans Java :

String city = "New York";
String movie = "Best ever";
String pet;
String emptyString = "";

Vous pouvez fusionner une ou plusieurs d'entre elles. Rassemblons quelques  strings  :

String firstFavoriteCity = "New York";
String secondFavoriteCity = "Buenos Aires";
String favorites = firstFavoriteCity + secondFavoriteCity; // -> "New YorkBuenos Aires"

Mais, il n'y a pas d'espace entre les deux. C'est bizarre, non ?

Rendons ce code plus lisible en concaténant, c'est-à-dire en mettant bout à bout des chaînes de caractères et des variables :

String firstFavoriteCity = "New York"
String secondFavoriteCity = "Buenos Aires"
String favorites = "My favorite cities are " +firstFavoriteCity+ " and "+secondFavoriteCity; // -> "My favorite cities are New York and Buenos Aires"

C'est beaucoup mieux maintenant ! Vous pouvez également concaténer d'autres types de données avec des chaînes de caractères, telles que des nombres.

Ah oui ? Mais comment je procède ?

String favoriteCity = "Buenos Aires";
int numberOfTrips = 5;
String story = "I've traveled to " +favoriteCity+ " " +numberOfTrips+ " times!"; // -> "I've traveled to Buenos Aires 5 times!"

Juste avant, nous avons utilisé l'opérateur   +  pour ajouter deux nombres. Ici, avec des chaînes de caractères, l'opérateur   +  peut être utilisé pour concaténer des chaînes et des nombres entiers. La concaténation fait référence à l'assemblage de chaînes de caractères ou de nombres, et de chaînes de caractères.

En résumé

Dans ce chapitre, vous avez vu les particularités de quelques types de données simples :

  • nombre entiers (mot-clé   int  ) ;

  • nombres décimaux (mots clés   float  et   double  ) ;

  • booléens (mot-clé   boolean  ).

Vous avez abordé un type complexe :

  • chaînes de caractères (mot clé   String  ).

Vous avez aussi appris à manipuler ces types :

  • vous pouvez effectuer des opérations numériques sur des nombres du même type ;

  • pour utiliser ensemble des nombres de types différents dans les opérations, utilisez  cast  pour qu'ils se comportent comme le type nécessaire ;

  • les Strings peuvent être mis bout à bout. Cela s'appelle la concaténation.

Dans le chapitre suivant, nous aborderons la fonction  main  ; c'est le point d'entrée de votre programme.

Écrivez une fonction

 

Dites bonjour au monde entier

Pour aborder ce chapitre, je dois vous présenter la notion de classe.

Une fonction peut être considérée comme un bloc de code avec un nom, qui exécute un service. Quand il s'agit d'une fonction   main  , le service effectué est en fait le programme lui-même ! Plutôt cool, non ? En d’autres termes, lorsque vous lancez votre programme, c’est la fonction  main  qui se lance. Elle est aussi appelée le point d’entrée.

Lorsqu'une fonction est située à l'intérieur d'une classe, elle s'appelle une méthode. Puisque tout le code est situé à l'intérieur de classes, vous pouvez utiliser les deux termes (fonctions et méthodes) de manière interchangeable.

Maintenant que vous connaissez la fonction   main  et que vous savez comment lancer un programme, il est temps d'écrire votre premier programme ! Traditionnellement, lorsque l'on apprend un langage pour écrire son premier programme, on cherche à afficher la chaîne de caractères Hello World! (Bonjour tout le monde, en français).

Si vous vous en souvenez – mais ce n’est pas si loin ! – je vous ai accueilli dans ce cours avec ce programme.

Regardons à nouveau  le code Java qui rend honneur à cette tradition :

package hello;

/** Ceci est une implémentation du message traditionnel "Hello world!"

* @author L'équipe Education d'OpenClassrooms

*/

public class HelloWorld {

    /** Le programme commence ici */

    public static void main(String[] args) {

        System.out.println("Hello World!");

    }

}

Décryptons ce code :

  • La première instruction,   package hello;  , est une déclaration de package. Ne tenez pas compte de cette ligne pour le moment.

  • La déclaration   public class HelloWorld   définit le nom de la classe comme étant  HelloWorld. En Java, l'ensemble du code doit se trouver à l'intérieur d'une classe.

  •  public static void main(String[] args)  . C'est le morceau de code que l'interpréteur Java recherche lorsque vous démarrez un programme.

  • Une instruction avec une classe utilitaire nommée   System  .  Ce genre de classe n'a pas besoin d'être instancié pour être utilisé. 

  • À l'intérieur de la méthode principale, vous trouverez l'instruction   System.out.println("Hello World !");  qui affiche le message attendu.

Mais, à quoi servent les lignes dans les caractères   /**  et   */   ?

Ce sont des commentaires de documentation, un code qui n’est pas utilisé lors de l'exécution du programme. C’est la version paragraphe de //. Cela vous permet de laisser des messages informatifs pour expliquer votre code (pour votre vous futur ou d’autres développeurs !)

En résumé, le code de démarrage d'un programme Java est contenu dans une fonction   main  (ou méthode). Cette fonction   main  est elle-même contenue dans une classe. Enfin, cette classe elle-même appartient à un package.

Maintenant que vous savez écrire du code, il est temps de l'exécuter !

Exécutez le programme à partir du terminal

En Java, il y a une correspondance directe entre :

  • les packages et les dossiers ;

  • les classes et les fichiers.

En effet, pour exécuter le programme sur votre ordinateur, vous devrez créer des dossiers qui correspondent à vos packages, et des fichiers qui correspondent à vos classes ! Pour le moment, nous avons écrit notre code Hello World! dans la méthode principale d'une classe HelloWorld. Cette méthode principale se trouve dans un package   hello  . Voyons maintenant ce que vous devez faire pour faire correspondre cela avec quelques fichiers et dossiers.

Voici les étapes principales :

  1. Tout d'abord, vous devez créer un dossier dans lequel vous allez mettre tout votre code. C'est ce qu'on appelle généralement le dossier root (racine).

  2. À l'intérieur de ce dossier racine, vous pouvez créer un dossier "hello" correspondant au nom de votre package.

  3. Ensuite, créez un fichier HelloWorld.java dans le dossier Hello, correspondant au nom de votre classe.

Vous voyez comment tout cela s'organise ? Package vers dossier, classe vers fichier. ✅
Une fois le fichier HelloWorld.java créé, vous pouvez saisir votre code Java. Utilisons le code de la section précédente :

package hello;
/** Ceci est une implémentation du message traditionnel "Hello world!"
* @author L'équipe Education d'OpenClassrooms
*/
public class HelloWorld {
    /** Le programme commence ici */
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

Lorsque tout le code est à l'intérieur du fichier, vous devez convertir ce code Java en code exécutable par une machine, que l'ordinateur peut comprendre.

Exécutable ? Mais qu'est-ce que ça veut dire ?

Quel que soit le langage de programmation que vous utilisez pour écrire votre code, il doit être traduit en un ensemble d'instructions qu'un ordinateur peut exécuter. C'est ce qu'on appelle le code machine.

Mais alors, pourquoi ne pas écrire des programmes directement en code machine ?

Même si le code machine est parfaitement lisible pour les ordinateurs, il serait très difficile à utiliser pour les êtres humains.

Pensez votre code comme une recette de cuisine. Vous pouvez utiliser un langage de tous les jours pour décrire les ingrédients et les étapes à suivre, ou vous pouvez utiliser leur composition chimique et un vocabulaire spécialisé pour décrire les différentes étapes. Cette dernière option pourrait être comprise par certains, mais la plupart d'entre nous ne pourraient pas cuisiner avec de telles instructions. Et même pour ceux qui comprennent, le processus serait très long !

Le langage dans lequel le code Java doit être transformé est appelé Bytecode. Pour transformer le code Java en Bytecode, il est nécessaire d'utiliser le compilateur javac.

Et c'est à ce moment-là que les dossiers commencent à être utiles ! En utilisant la console/le terminal, naviguez jusqu'au dossier racine   root  de votre programme, et exécutez la commande suivante :

$ javac helloHelloWorld.java

La commande javac est en fait elle-même un programme. Si vous êtes sous Windows, elle sera nommée javac.exe.

Cette commande crée un fichier HelloWorld.class dans le dossier Hello. Ce fichier est un fichier binaire (vous ne pouvez pas l'ouvrir dans un éditeur de texte). Vous pouvez maintenant exécuter le programme avec la commande java (ou java.exe sous Windows) :

$ Java hello.HelloWorld Hello World!

Concrètement, lorsque vous développez, vous utilisez un environnement de développement (IDE) pour vous simplifier la vie. L'exécution de votre programme en cours de conception peut très bien se faire avec cet outil magique. Il se chargera de vous dire où sont les bugs, d'interpréter et d'exécuter tout seul le programme.

Dans le reste de ce chapitre, nous allons nous concentrer sur la façon d'organiser votre code afin que votre fonction  main  reste aussi petite que possible.

Organisez votre code de manière optimale

Le but de votre fonction   main  est de démarrer votre programme.

Dans les bonnes pratiques, il est d’usage que la méthode main soit la plus courte possible, en appelant uniquement les méthodes nécessaires.

Comme nous l'avons dit au début de ce chapitre, il existe deux types de classes que vous pouvez écrire et utiliser.

Utilisez des classes en tant que modèles

Vous pouvez définir des types complexes qui regroupent différents attributs représentant un concept nommé. Ce sont des classes de modèles. Vous les écrivez souvent pour modéliser le domaine de votre application : ce pour quoi vous écrivez votre programme.

Exemple : la classe   String  que vous utilisez pour stocker et manipuler les chaînes de caractères dans votre programme. Cette classe est disponible dans le package   java.lang  , qui est disponible depuis n'importe quelle partie de votre code.

Comment se fait-il que   String  soit une classe et non un type primitif tel que   int  ou   double    ?

String  est une classe non seulement parce que son nom commence par une lettre majuscule, mais aussi parce qu'il définit un état et un comportement :

  1. Son état est la chaîne de caractères que vous stockez. La valeur réelle est définie pour chaque objet lorsque vous l'instanciez.

  2. Son comportement est l'ensemble des méthodes que la classe   String  définit, et qui vous permettent d'opérer sur la chaîne que vous stockez.

Voyons cela en action :

Comment savoir quels comportements sont réellement disponibles ?

Vous vous souvenez de la Javadoc ? Chaque fois que vous écrivez une classe, vous êtes censé la documenter dans les marqueurs   /**  et   */  . Cela permet de générer automatiquement une page de documentation HTML. Les développeurs Java ont fait cette page pour vous, et l'ont rendue disponible sur le site officiel. Cette documentation va vous permettre de trouver les fonctionnalités disponibles rapidement ! Pratique, non ?

Jetez un coup d'œil à la Page Javadoc sur les  String  . Pouvez-vous trouver les méthodes   toUpperCase()  et   contains()  ?

Entraînez-vous à trouver des méthodes dans la page Javadoc, et faites en sorte que l'utilisation du site web Javadoc devienne un réflexe pour vous ! Cela vous permettra de gagner du temps par la suite.

Voyons maintenant le deuxième type de classes que vous allez définir et utiliser : les classes utilitaires.

Nettoyez votre fonction main 

Dans certains cas, vous n'aurez même pas accès à la fonction   main  ! Cela peut se produire si vous utilisez des frameworks, comme le kit de développement d'Android pour le développement mobile, ou le Spring Framework pour le développement web. Les frameworks sont des outils qui fournissent aux développeurs les fonctionnalités de base sur lesquelles ils peuvent s'appuyer, généralement sous la forme d'un ensemble de classes.

Penchons-nous maintenant sur votre fonction main  et prenons-en le contrôle. Nous allons la rendre aussi propre et nette que possible. Voici une implémentation « propre et nette » de notre programme HelloWorld :

package cleanHello;

/** Ceci est une implémentation du message traditionnel "Hello world!"
* @author L'équipe Education d'OpenClassrooms
*/
public class CleanWorld {
    
   /** Le programme commence ici */
   public static void main(String[] args) {
      sayHelloTo("world");
   }
   
   /** affiche le message "hello" au destinataire fourni
   *
   * @param recipient
   */
   private static void sayHelloTo(String recipient) {
      System.out.println("Hello " + recipient);
   }
   
}

Comme vous pouvez le voir, la classe   CleanWorld  définit deux méthodes :

  1. main  est le point d’entrée du programme. Son seul job, c'est de transmettre le travail à la méthode sayHello avec l'argument dont elle a besoin. Dans notre cas, c'est le destinataire prédéfini de notre hello : the world !

  2. La méthode   sayHello  imprime la chaîne « Hello » et ajoute la valeur fournie à la variable destinataire   recipient  lorsqu'elle est appelée par la méthode  main  .

En termes de fonctionnalité, rien n'a changé. Cependant, vous pouvez maintenant ajouter plus de logique au message que vous allez afficher, en changeant la méthode   sayHello  et en personnalisant le nom du destinataire.

Nous allons ajouter des fonctionnalités dans les chapitres suivants ! Pour cela, vous devez ajouter plus de logique à votre boîte à outils de programmation Java. Le chapitre suivant traite de ce premier outil : les conditions !

En résumé

  • Les programmes Java sont structurés en packages et en classes.

  • Aucun code n'est écrit en dehors d'une classe, ce qui signifie que toutes les fonctions sont des méthodes en Java.

  • Les packages sont mappés dans des dossiers et les classes dans des fichiers.

  • La commande javac convertit le code Java en Bytecode.

  • La commande java exécute le programme actuel en exécutant la fonction   main  dans la classe fournie.

  • Il existe deux types de classes :

  1. Les classes modèles qui sont utilisées comme modèles pour l'instanciation des objets.

  2. Les classes utilitaires qui contiennent des méthodes statiques qui peuvent être appelées directement sur la classe.

  • Vous pouvez accompagner vos classes et méthodes avec des commentaires de documentation, écrits entre  /**  et   */  , pour générer une page HTML avec toute la documentation de la classe, appelée un Javadoc. La méthode   main  peut vous être masquée si vous utilisez un framework.

  • Les principes du code propre exigent qu'aucune logique ne soit écrite à l'intérieur de la méthode   main  . Tout le travail doit être délégué à des fonctions bien nommées.

Dans le prochain chapitre, nous aborderons la portée des variables en Java. Certaines peuvent être localisées dans une méthode ou une section de code, alors que d'autres ont une portée plus globale.

Saisissez la portée des variables

Dans ce chapitre, nous allons nous rendre compte que chaque variable déclarée en Java a une portée, c'est-à-dire un champ d’accessibilité dans lequel elle peut être utilisée.

Comprenez le principe de portée d'une variable

Chaque variable n'est disponible (et accessible) que dans le contexte dans lequel elle a été déclarée. Pour déterminer le contexte, reportez-vous aux marques d'ouverture et de fermeture les plus proches qui entourent la déclaration. La plupart des langages de programmation utilisent des accolades ouvrantes et fermantes (  { }  ) pour marquer le début et la fin d'un bloc de code. 

Jetez un œil à cet exemple :

La portée d'une variable correspond à ce qui se trouve à l'intérieur des accolades.
La portée d'une variable

Il n'est pas nécessaire de comprendre chaque morceau de code ici. Il vous suffit de vous concentrer sur les accolades ouvrante et fermante. Quand nous parlons de la disponibilité d'une variable dans un contexte, nous faisons référence à la portée (scope). Ici, vous pouvez voir que la variable   root  a été déclarée entre les deux accolades (ouvrante et fermante) entourées en violet. La portée de cette variable est tout ce qui se trouve entre ces deux accolades. Autrement dit, la classe MyScopeExample connaît root, mais ce qui est à l'extérieur ignore son existence. 

La portée d'une variable peut être locale ou globale, en fonction de l'endroit où la variable est déclarée. Une variable globale peut être disponible pour toutes les classes et méthodes d'un programme, alors qu'une variable locale ne peut être disponible que dans la méthode dans laquelle elle est déclarée :

Il peut y avoir une portée à l'intérieur d'une plus grande portée. Elles sont toujours délimitées par des accolades ouvrantes et fermantes.
Une portée locale au sein d'une portée globale

Ici, nous avons un peu élargi le code et étendu notre premier bloc de code pour en inclure un autre ! Si vous tracez les lignes violettes, vous pouvez voir que les parenthèses du premier bloc englobent tout le code du second. Ensuite, vous pouvez voir qu'une nouvelle variable,   spy  , a été déclarée dans le cadre local vert.

Puisque la variable   root  a été déclarée dans la portée globale, cela signifie qu'elle est accessible à tout ce qui est entre parenthèses violettes, y compris tout ce qui est déclaré dans la portée locale. Dans le deuxième bloc de code, vous pouvez voir une ligne juste en dessous de la déclaration de la variable «  spy  » qui utilise   root  . Cela est autorisé !

Cependant, tout ce qui est dans la portée locale n'est pas disponible pour la portée globale, ni aucun autre bloc de code local. Prenons un autre exemple :

Il peut y avoir plusieurs portées locales à l'intérieur d'une plus grande portée globale.
Deux portées locales au sein d'une portée globale

Ici nous avons ajouté un autre bloc de code, qui a sa propre portée locale et sa propre variable,   anotherSpy  . Maintenant, regardez la dernière ligne de notre bloc variable « spy  » :

System.out.println(anotherSpy); // Erreur

On dirait qu'il y a une erreur ! C'est parce qu'elle essaie d'utiliser la variable   anotherSpy  . Mais ce n'est pas possible car   anotherSpy  n'est ni dans la portée globale ni dans la même portée locale. Cela signifie que ce bloc de code ne peut pas y accéder.   anotherSpy  est disponible uniquement dans le bloc de code dans lequel il a été déclaré.

L'inverse est également vrai. Vous voyez que la dernière ligne de notre dernier bloc de code présente elle aussi une erreur :

System.out.println(spy); // Erreur

Ici, le code essaie d'utiliser la variable   spy  d'un autre bloc de code. Mais ce n'est pas possible car   spy  n'est pas dans la même portée que le bloc de code qui essaie de l'utiliser.

Déterminez la portée de la variable dans les classes

Lorsque vous déclarez une classe, les mêmes règles générales concernant la portée s'appliquent : chaque variable n'est accessible qu'au sein de son bloc de déclaration. Expérimentons avec une classe   Unicorn  :

Cette structure de code est appliquée à la déclaration de la classe Unicorn et montre les différentes portées (deux locales à l'intérieur d'une globale).
Exemple avec une classe Unicorn

Tout comme dans notre premier exemple, il y a des variables de classe globale ainsi que des variables locales. Revoyons cela plus en détail :

  • les variables   height  et   power  sont des champs de la classe et sont accessibles partout dans la classe ;

  • la variable   minutesToSleep  n'est accessible que dans le cadre local du bloc de code dans lequel elle est déclarée ;

  • la variable   minutesToRun  n'est accessible que dans le cadre local du bloc de code dans lequel elle est déclarée.

La portée d'une variable limite (par définition) son accessibilité. Cependant, les champs de classe sont accessibles en dehors de la classe et peuvent être utilisés par tout autre bloc de code.

Dans notre exemple, ce sont les champs   height  et   power  . Si nous déclarons une variable Unicorn (licorne), nous pouvons lire ou modifier ces valeurs :

Unicorn unicorn = new Unicorn();

System.out.println("I know it's height: "+unicorn.height);

// et peut changer son pouvoir !
unicorn.power = 0; // pas drôle!

Le fait d'être capable de jouer avec les variables de classe peut avoir des conséquences graves. La bonne nouvelle, c'est que vous pouvez le contrôler ! Avant de vérifier comment, assurez-vous de vous exercer à déterminer la portée des variables.

Implémentez un contrôle d'accès

Nous allons utiliser cette idée de contrôle d'accès en implémentant un accès restreint à une variable, une classe, un module ou un fichier. Vous savez déjà ce que sont une classe et un fichier !

D'ailleurs, un fichier de code est aussi appelé un fichier source.

Chaque environnement de développement fournit un certain nombre de frameworks. Le fait est que la mise en œuvre de ces frameworks dépasse de loin ce que les développeurs qui les utilisent peuvent voir et utiliser. Cela se fait en limitant l'accès aux détails de l'implémentation, également connue sous le nom d'implémentation du contrôle d'accès.

Désignez un niveau de contrôle

En Java, vous devez utiliser un des mots clés suivants pour désigner un niveau de contrôle :

  • public : visible pour tous et par conséquent le moins restrictif ;

  • protected (protégé) : visible pour le package et l'ensemble de ses sous-classes ;

  • package-protected (protégé par paquet) : généralement visible uniquement par le package dans lequel il se trouve (paramètres par défaut). Ne pas mettre de mot clé déclenche ce niveau de contrôle ;

  • private (privé) : accessible uniquement dans le contexte dans lequel les variables sont définies (à l'intérieur de la classe dans laquelle il est situé).

La mise en place de ces restrictions distinctes facilite grandement le développement. Vous n'avez pas à vous soucier de la visibilité non désirée de votre implémentation, ni, plus encore, des modifications non désirées.

En plus de la sécurité, la spécification de niveaux de contrôle pour les membres du groupe permet une meilleure lisibilité. Si un développeur prépare un fichier source, les éléments pouvant être utilisés en externe seront ainsi toujours visibles.

Déterminez une hiérarchie de contrôle

Un élément peut avoir le même niveau de contrôle, ou un niveau de contrôle plus restrictif que son élément contenant :

public class PublicClass {
    public boolean publicProperty = true;
    int internalProperty = 0; //par défaut pour package-private
    private String fileprivateProperty = "Hello!"
    private static void privateMethod() {
    }
}

Dans l'exemple ci-dessus, la classe est déclarée   public  . Puisque la classe est l'élément contenant, cela signifie que tous les éléments de cette classe peuvent être du même niveau d'exposition d'un niveau inférieur. Dans ce cas, cela comprend tous les niveaux.

Si vous déclarez une classe comme   private  , ses éléments ne peuvent être que   package-private  ou   private  :

class PrivateClass {
    int internalProperty = 0; // assigne automatiquement package-private par défaut
    protected defaultProperty = true; // assigne automatiquement package-private
    public boolean publicProperty = true; // convertit automatiquement en package-private
    private String fileprivateProperty = "Hello!"; //disponible seulement pour la classe
    private static void privateMethod() {
    }
} 

Dans l'exemple ci-dessus, nous avons ajouté un attribut sans mot clé de contrôle d'accès explicite. Dans ce scénario, il prend par défaut le niveau de l'élément contenant. Dans ce cas, c'est notre classe, donc il prend le niveau de   PrivateClass  .

Une classe de premier niveau ne peut pas être marquée comme private (personne ne pourrait la voir, et donc elle ne pourrait pas être utilisée), mais la définir comme « par défaut » la placera dans le niveau package-protected. Lors de la déclaration d'une variable d'une classe, si le niveau de contrôle du contexte de déclaration est supérieur à celui de la classe, la variable doit également avoir un niveau de contrôle explicite.

Déclarons une variable PrivateClass :

PrivateClass a = new PrivateClass(); // Erreur
private PrivateClass b = new PrivateClass(); // Ok
private PrivateClass c = new PrivateClass(); // Ok

Comme vous pouvez le voir, si le niveau d'accès par défaut du contexte d'une variable est supérieur à une classe que vous lui affectez, vous devez explicitement spécifier le niveau de la variable comme étant identique ou inférieur à celui de la classe.

En résumé

  • La portée (scope) d'une variable est la zone de code où elle a été déclarée.

  • La portée variable générale s'applique à tous les blocs de code, y compris les classes.

  • Un autre moyen nécessaire pour contrôler l'accès aux variables et aux fonctions est d'utiliser des niveaux de contrôle : public, protected, package-protected et private.

Nous verrons dans le prochain chapitre l'écriture des boucles de traitement en Java.

Écrivez une boucle dans vos fonctions

Imaginez que vous ayez un bloc de code que vous devez répéter plusieurs fois. Pourquoi pas pour afficher un message dans la console, par exemple un bonjour, répété cinq fois ? Vous pouvez écrire cinq fois la commande pour le faire. Mais subitement, vous ne souhaitez plus dire “bonjour”, mais “bonjour à tous !”. Il faudrait alors modifier cinq fois le bloc dans votre code. Comme tout développeur vous le dira, nous détestons les répétitions (et sommes un peu fainéants !).

Et c’est pour cela que les boucles existent !

Utilisez des boucles énumérées pour un nombre connu d'itérations

Les boucles énumérées sont des boucles qui sont utilisées si vous savez à l'avance combien de fois vous voulez faire une boucle. En Java, cela s'appelle des boucles for.

Avec elles, vous pouvez indiquer le nombre d'itérations à effectuer :

  1. En tant que valeur entière.

  2. Comme résultat d'une expression qui génère une valeur entière.

Découvrez les boucles  for  avec une valeur entière

Voici un exemple d'une boucle for qui répète une instruction cinq fois :

for (int i=0; i<5;i++) {
   System.out.println("Clap your hands!");
}

Dans cet ensemble d'instructions, nous avons une variable de dénombrement   i  qui est responsable du nombre d'exécutions. Le code équivalent sans utiliser une boucle serait :

System.out.println("Clap your hands!");
System.out.println("Clap your hands!");
System.out.println("Clap your hands!");
System.out.println("Clap your hands!");
System.out.println("Clap your hands!");

La syntaxe générale de l'instruction for est la suivante :

for (initialisation; terminaison; increment) {
// code à répéter
}
  1. L'initialisation est une expression qui s’initialise au début de la boucle. Elle déclare et assigne généralement un itérateur. Dans notre exemple, nous déclarons un itérateur nommé   i  de type   int  avec une valeur de   0  .

  2. La terminaison est l’expression qui est évaluée avant chaque exécution de boucle. Si elle est évaluée sur « false », la boucle s'arrête. Dans notre exemple, l'expression de terminaison est   i<5  , ce qui signifie que la boucle s'arrête lorsque   i  atteint la valeur cinq.

  3. L’incrément est une expression qui est évaluée chaque fois qu'une itération de la boucle est effectuée. Dans notre exemple, l'incrément est   i++  . Cela signifie que nous ajoutons 1 à la valeur de i à chaque fois que nous passons par notre boucle.

  4. La liste des instructions est située entre   {  et   }  . C'est le code qui est exécuté chaque fois que la boucle est exécutée.

Le booléen peut prendre deux valeurs : true ou false.  Dans notre boucle for, tant que la condition de terminaison est true, la boucle tourne. Dès que la condition n’est plus vérifiée, elle est donc false, et on sort de la boucle !

Découvrez les boucles  for  avec les collections

Vous pouvez utiliser les boucles d'énumération si vous avez besoin d'effectuer une itération sur un tableau ou une collection. Voici un exemple pour les tableaux :

int[] myArray = new int[]{7,2,4};
for (int i=0; i<myArray.length; i++) {
    System.out.println(myArray[i]);
}

Appeler la propriété  length  d’un tableau retourne le nombre d'éléments de ce tableau. Dans cet exemple, il est utilisé comme condition de terminaison de la boucle "for". 

for (int i=0; i<=0; i--) {
    System.out.println(“i sera toujours inférieur à 0!”);
}

Parfois, on ne veut pas spécifier explicitement combien de fois la boucle doit tourner, mais juste qu’elle doit couvrir tous les éléments d’un tableau. Dans ce cas, Java fournit une construction améliorée appelée  for each  , qui a la syntaxe générale suivante :

for (int number: myArray){
   System.out.println(number);
}

Avec la boucle  for each  , il vous suffit de définir une variable du type contenu par le tableau, ou de la collection que vous souhaitez mettre en boucle. Cette variable se verra attribuer la valeur de chaque élément du tableau ou de la collection, jusqu'à ce que vous ayez atteint la fin.

En français, cet exemple donne : Pour chaque int, qui sera à chaque tour de boucle du tableau myArray rangé dans une variable appelé  number  , affiche-moi en console la valeur de  number  .

Répétez la boucle jusqu'à atteindre une condition 

Dans ce cas, la boucle doit continuer tant que la condition du  while  reste vraie. Le nombre de répétitions n'est pas défini par les limites inférieure et supérieure d'un énumérateur, mais par une condition telle que celle d'une instruction  if.

Appréhendez la boucle  while

Voici à quoi ressemble la syntaxe d'une boucle while :

while (logicalExpression) {
// liste de déclarations
}

Elle peut être interprétée comme « tant que l'expression logique est vraie, répétez les instructions ».

Voilà comment cela fonctionne en détail :

  1. Le programme vérifie que   logicalExpression  est vrai (  true  ).

  2. Si l’expression est fausse (  false  ) : les instructions sont ignorées. Vous n'entrez même pas dans le corps de la boucle située entre   {  et   }  .

  3. Si c'est true : la liste des instructions à l'intérieur de   {  et   }  sont exécutées.

  4. Une fois les instructions exécutées, vous revenez à la première étape.

Voyons un exemple concret :

int numberOfTrees = 0;
while (numberOfTrees < 10) {
   numberOfTrees += 1;
   System.out.println("I planted " + numberOfTrees + " trees");
}

System.out.println("I have a forest!");

Cela donnera le résultat suivant :

I planted 1 trees
I planted 2 trees
I planted 3 trees
I planted 4 trees
I planted 5 trees
I planted 6 trees
I planted 7 trees
I planted 8 trees
I planted 9 trees
I planted 10 trees
I have a forest!

À chaque tour de boucle, le nombre d'arbres   numberOfTrees  est incrémenté de 1. Lorsque la variable atteint la valeur   10  , le nombre d'arbres  numberOfTrees  <10  n'est plus vrai. Donc, la boucle se termine, et le reste du programme continue. Dans ce cas, le programme affiche "  I have a forest!  " (J'ai une forêt !).

boolean theSunIsUp = true;
while (theSunIsUp) {
   print ("Stay awake...forever!");
   // theSunIsUp ne change jamais
}
// nous n'atteignons jamais ceci
print ("Go to sleep!");

C'est une erreur courante et facile à commettre. Alors, faites attention !

Comme vous pouvez le voir, la condition while est vérifiée AVANT que le bloc de code correspondant puisse être exécuté, même une seule fois ! Cela veut dire que si la condition n’est pas vérifiée, le bloc de code peut ne jamais être exécuté.

Découvrez la boucle  do... while

La boucle do... while est très similaire à la première, mais la condition est placée à la fin du bloc de code correspondant. De cette façon, le bloc de code sera toujours exécuté au moins une fois.

Voici à quoi ressemble la syntaxe :

do {
// instructions
} while(logicalExpression);

Prenons un exemple :

int pushUpGoal = 10;
do{
   print ("Push up!");
   pushUpGoal -= 1;
} while(pushUpGoal > 0);

De cette façon, au moins une pompe (un « push-up ») est effectuée avant même que la condition soit vérifiée. Modifions cela un petit peu pour montrer la différence par rapport à la boucle while d'origine :

Prenons un exemple :

// Boucle "While"
int pushUpGoal = 0;
while(pushUpGoal > 0) {
   System.out.println ("Push up!");
   pushUpGoal -= 1;
   }
// Boucle "do/while"
int pushUpGoal = 0;
do{
   System.out.println ("Push up!");
   pushUpGoal -= 1;
} while (pushUpGoal > 0);

Vous pouvez voir que l'utilisation de la boucle while d'origine n'effectuera pas de « push-up ». La boucle do... while provoquera un push-up une seule fois.

Ignorez quelques instructions à l'intérieur d'une boucle

Dans chaque type de boucle, il peut y avoir des situations où vous souhaitez sauter certaines itérations, ou interrompre toute la boucle prématurément à une certaine condition.

Par exemple, vous pouvez vouloir répéter quelque chose 10 fois, mais ignorer (ou ignorer partiellement) si la valeur est égale à   2  ou   5  . En Java, pour ignorer une itération dans la boucle, utilisez une instruction  continue  :

for ( int i=0; i <10; i++) {
// déclarations exécutées à chaque itération
   if(i == 2 ||i == 5) {
   continue;
   }
System.out.println(“Valeur de i : “ + i + “.”);
}

Vous pouvez également interrompre complètement la séquence, par exemple si vous voulez trouver un élément dans un tableau, et arrêter de chercher une fois cet élément trouvé :

En Java, pour interrompre une séquence d'exécution, utilisez une instruction  break  :

int [] myArray = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };

for (int i =0; i<myArray.length;i++) {
   if (myArray[i] == "50") {
   System.out.println ("J’ai trouvé mon  " +basket[i]+ " !");
   break;
   }
System.out.println ("J’en suis à " +basket[i]+ " ...");
}

Une fois que vous avez trouvé l’élément que vous recherchiez, arrêtez de parcourir le reste du tableau.

À vous de jouer !

Prêt à coder ? Pour accéder à l’exercice, suivez ce lien.

En résumé

  • Les boucles d'énumération exécutent un ensemble d'instructions un nombre de fois fixe, basé sur les valeurs limites inférieure et supérieure de l'énumérateur.

  • Les boucles conditionnelles exécutent un ensemble d'instructions jusqu'à ce qu'une condition définie soit remplie.

  • Une erreur courante à surveiller avec les boucles conditionnelles : les boucles infinies !

  • Les itérations dans une boucle peuvent ignorer certaines instructions à l'intérieur de la boucle en utilisant la commande   continue  .

  • Le cycle de boucle peut être interrompu et la boucle peut être interrompue prématurément à l'aide de la commande  break  .

Dans le chapitre suivant, nous allons voir comment contrôler le déroulement d'un programme en appliquant des conditions qui vont gérer son comportement selon les situations. Vous me suivez ? On y va !

Contrôlez le déroulement d'un programme avec des conditions

Au fur et à mesure que vous allez écrire des programmes de plus en plus sophistiqués, l'écriture de lignes de code qui doivent s'exécuter l'une après l'autre selon une séquence prédéfinie ne sera plus suffisante. C’est là que vous aurez besoin des conditions !

Reprenons le programme Hello World sur lequel nous avions travaillé :

package hello;

/** Ceci est une implémentation du message traditionnel "Hello world!"
* @author L'équipe Education d'OpenClassrooms
*/
public class HelloWorld {

/** Le programme commence ici */
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
    
}

Nous avons dit bonjour au monde entier. Ne serait-il pas agréable d'être un peu plus précis, par exemple dire bonjour à une personne réelle ?

Affichez des informations précises si elles sont disponibles

Au démarrage du programme, il se peut que vous ne connaissiez pas le nom d'un utilisateur. Alors pourquoi ne pas utiliser quelque chose comme :

  • if (si) nous connaissons le nom de la personne, l'afficher ;

  • else (sinon), continuer à dire bonjour au monde entier.

Voilà à quoi servent les conditions.

Comment pouvons-nous donc connaître le nom de cette personne ?

Revenons à la toute première déclaration qu'un programme exécute, la signature de la fonction main :

public static void main(String[] args) {

Avez-vous remarqué   String[] args  à l'intérieur des parenthèses    ()  ? Nous avons une variable   args  définie avec le type   String[]  , qui est un tableau de chaînes de caractères (nous y reviendrons dans la prochaine partie). Cela signifie que la fonction main est capable de recevoir des arguments que nous pouvons utiliser lorsque le programme est lancé.

Vous vous souvenez de la commande qui démarre un programme Java depuis le terminal ? Si on reprend notre exemple Hello World, nous avions suivi les étapes suivantes :

  1. Compiler le programme avec la commande javac.

  2. Exécuter le programme avec la commande java.

La magie qui consiste à envoyer les arguments à votre programme se déroule durant la deuxième phase. Au lieu de simplement fournir le nom de la classe contenant la méthode main, vous pouvez ajouter des  arguments, séparés par un espace. Voici ce que vous pouvez faire :

$ java hello.HelloWorld Esther

Après le nom de la classe, nous avons ajouté  Esther  , une  String  . Au démarrage du programme, cette chaîne est contenue dans le tableau  args  qui fait partie de la signature de la fonction principale. Cependant, notre code original ne s'en sert pas. Nous devons le changer pour :

  1. Vérifier si le tableau   args  contient une valeur (la propriété   length  fournie par les tableaux peut nous aider ici).

  2. Si c'est le cas, appeler la méthode   sayhelloTo  avec la valeur qu'elle contient.

  3. Sinon, continuer à appeler la méthode avec la chaîne   world  .

Voilà ce que ça donne :

package conditions;

/**Ce programme affiche
* - un message Hello personnalisé si un argument a été envoyé lors de l'exécution du programme
* - le message traditionnel "Hello World!" si ce n'est pas le cas
* @author L'équipe Éducation d'OpenClassrooms
*/

public class ConditionalHello {

/** Le programme commence ici
* @param args arguments envoyé avec la ligne de commande
*/

   public static void main(String[] args) {

       if (args.length==1) {
         sayHelloTo(args[0]);
       }
       else {
         sayHelloTo("world");
       }

}

/** affiche le message hello au destinataire fourni
* @param recipient
*/

   private static void sayHelloTo(String recipient) {
       System.out.println("Hello " + recipient + "!");
    }

}

Maintenant :

  1. Compilez le programme avec la commande javac.

    $ javac conditions/ConditionalHello.java
    
  2. Exécutez le programme sans argument.

    $ java conditions/ConditionalHello Hello world!
    
  3. Exécutez le programme en ajoutant "Esther" après le nom de la classe.

    $java conditions/ConditionalHello Esther Hello Esther!
    

Ça marche ! Voyons plus en détail comment fonctionne réellement cette affectation conditionnelle If/Else.

Testez vos conditions avec des booléens

En langage Java, pour valider une condition, vous utilisez les booléens.

Vous vous souvenez des booléens ?

boolean isLearningJavaWithOpenclassrooms = true;

Ce qui importe vraiment, c'est que la condition du if soit résolue par un booléen. Cette condition peut être exprimée de la façon suivante :

  1. Une valeur true ou false. Par exemple,   if(true)  .

  2. Une variable de type  boolean . Par exemple,   if(myVar)  où   myVar  est de type  boolean .

  3. Une expression qui se résout en une valeur booléenne. Cette expression peut être aussi simple que le résultat d'un appel de méthode.

Par exemple :

String weatherToday="The weather is good";
String weatherTomorrow="The weather is good";
weatherToday.equals(weatherTomorrow); // -> true

equals  est une méthode de la classe   String  qui permet de comparer deux chaînes.

En effet, on ne compare pas deux objets via  ==  .  ==  vérifie si deux objets ne font qu’un (la même adresse en mémoire), et non s’ils ont le même contenu.  La méthode equals, par opposition, compare le contenu. Elle peut donc être utilisée comme une condition.

Pour produire un booléen, vous pouvez également utiliser des opérateurs de comparaison.

Utilisez les opérateurs de comparaison

Comme leur nom l'indique, les opérateurs de comparaison sont utilisés pour comparer deux valeurs. Ils sont au nombre de six :

  • ==  égal à (exactement le même) ;

  • !=  non égal à (différent, de quelque façon que ce soit) ;

  • <  inférieur à ;

  • <=  inférieur ou égal à ;

  • >  supérieur à ;

  • >=  supérieur ou égal à.

Voici quelques exemples de comparaisons numériques :

2 == 2 // -> true
2 == 3 // -> false

4 != 4 // -> false
4 != 5 // -> true

1 < 2 // -> true
1 < 1 // -> false
1 <= 1 // -> true

3 > 4 // -> false
5 > 4 // -> true
5 >= 4 // -> true

Enfin, vous voudrez peut-être avoir des conditions plus compliquées, où la décision dépend du résultat d'une combinaison d'expressions différentes. C'est ici que vous utiliserez les opérateurs logiques.

Utilisez les opérateurs logiques

Ces opérateurs vous permettent de combiner des valeurs booléennes : soit des valeurs booléennes spécifiques, soit des résultats d'expressions. Ils sont au nombre de trois :

  • && ET logique.
    Le résultat n'est vrai que si toutes les parties participantes sont vraies.
    Exemple : le résultat de   expression1 && expression2  n'est vrai que si   expression1  est vraie ET   expression2  est également vraie.

  • ||  OU logique.
    Le résultat est vrai si au moins une des parties participantes est vraie.
    Exemple : le résultat de   expression1 || expression2  est vrai si   expression1  est vraie OU   expression2  est vraie. Il en sera de même si les deux expressions sont vraies !

  • !  NON logique.
    Il inverse simplement l'expression donnée.
    Le résultat de   !expression1  est vrai si   expression1  est fausse ; le résultat est faux si   expression1  est vraie.

Voici quelques exemples :

true && true // -> true
true && false // -> false
false && false // -> false

true || false // -> true
true || true // -> true
false || false // -> false

!true // -> false
!false // -> true

Les mêmes règles s'appliquent si plus de deux expressions sont enchaînées les unes après les autres :

true && true && true // -> true
true && true && false // -> false

true || false || false// -> true
false || false || false// -> false

Comme pour les opérateurs numériques, les opérateurs logiques respectent la priorité des opérations : l'opérateur d'inversion   !  vient en premier, puis l'opérateur ET   &&  et enfin, l'opérateur OU  ||  . Par exemple :

false || true && true // -> true

!false && true || false // -> true

Comme pour les opérateurs numériques, utilisez les parenthèses (  ()  ) pour changer l'ordre :

(true && false) || true // -> true

!(true && false || !true) // -> true

Pour vous entraîner, pouvez-vous calculer le résultat des expressions suivantes ?

!true && false
!(true && false)
4 < 3 || 4 >= 4
(!(1 == 2) || 3 != 3) && 35 > 34

Dans notre exemple Hello World, nous avons défini une alternative.

Et si on veut prendre des décisions plus complexes, basées sur plusieurs valeurs possibles ?

Gérez une chaîne de conditions

Nous avons personnalisé notre message de bienvenue, mais que faire si nous voulons plaire aux personnes ayant plus d'un prénom ?

Une première possibilité consiste à créer une chaîne de conditions. Voici la forme générale :

if(condition1) {
// instructions
}
else if(condition2) {
// instructions
}
else {
// instructions
}

Dans notre exemple Hello World, nous pourrions envoyer deux chaînes de caractères après le nom de la classe lors de l'exécution du programme. Cela signifierait que le tableau   args  contient deux éléments. Nous pourrions alors envoyer une chaîne contenant la concaténation de ces deux chaînes à notre fonction  sayHelloTo  :

public static void main(String[] args) {
    if (args.length==1) {
        sayHelloTo(args[0]);
    } else if (args.length==2) {
        sayHelloTo(args[0] + "-" + args[1]);
    } else if (args.length==3) {
        sayHelloTo(args[0] + "-" + args[1] + "-" + args[2]);
    } else {
        sayHelloTo("world");
    }
}

Dans cet exemple, notre code évalue les différentes valeurs d'une condition particulière (la taille du tableau   args  ). Dans ce cas, Java fournit une construction spécifique qui ne fait l'évaluation qu'une seule fois : l'instruction switch.

Utilisez l'instruction  switch

Parfois, il y a des circonstances où vous avez un ensemble plus long de conditions à remplir, et où l'une d'elles peut être true dans la séquence.

Réécrivons notre exemple if, else if et else en utilisant l’instruction switch :

public static void main(String[] args) {
    switch(args.length) {
        case 0: // aucun argument n'a été envoyé
            sayHelloTo("world");
            break;
        case 1: // l'utilisateur a fourni un argument dans le terminal
            sayHelloTo(args[0]);
            break;
        case 2: // l'utilisateur a fourni 2 arguments
            sayHelloTo(args[0] + "-" + args[1]);
            break;
        default: // l'utilisateur a fourni plus d'arguments qu'on peut en gérer !
            System.out.println("Sorry, I don't know how to manage more than 2 names!");
    }
}

Avec l'instruction  switch  :

  • l'expression conditionnelle   args.length  n'est évaluée qu'une seule fois ;

  • chaque cas compare le résultat de l'expression à une valeur spécifique ;

  • par défaut, une fois qu'un cas est évalué true, tous les cas ci-dessous sont également validés en cascade. Pour éviter cela, terminez chaque cas par une instruction   break;  ;

  • la clause Default à la fin est validée si aucun des autres cas n’est validé.

L'instruction switch rend votre intention plus claire qu'une chaîne de if/else, et vous permet d'évaluer l'état une seule fois. C'est cool. 😎 Mais ce n'est pas fini ! Encore un autre type de données : les énumérations

Utilisez le type énumération

Les énumérations sont des listes de cas prédéfinis destinés à vous aider pendant le développement. Elles améliorent la lisibilité du code et diminuent les risques d'erreurs, en bloquant par exemple les valeurs que peut prendre une variable.

Prenons un exemple où nous définissons toutes les directions possibles (nord, est, sud, ouest) dans une énumération, et utilisons-les dans un  switch  .

Voici dans cet exemple l’énumération :

enum Direction {
north, east, south, west;
}

Comme le switch doit couvrir tous les cas possibles, nous pouvons utiliser deux approches.

Approche n°1 : lister tous les cas d'énumération en switch
public class myDirection {

    /** listez toutes les directions possibles */
    enum Direction {
        north, east, south, west;
    }

    /** trouvez le nord */
    public static void main(String[] args) {
        Direction direction = Direction.north;
        switch (direction) {
            case north:
                System.out.println("You are heading north");
                break;
            case east:
                System.out.println("You are heading east");
                break;
            case south:
                System.out.println("You are heading south");
                break;
            case west:
                System.out.println("You are heading west");
                break;
        }
    }
}

Dans ce cas, nous couvrons toutes les directions, nous n'avons théoriquement pas besoin de la clause   default  .

Approche n°2 : définir un cas d'utilisation en particulier

Définissons le cas d'utilisation où nous ne devrons nous diriger que vers le nord :

public static void main(String[] args) {
    Direction direction = Direction.north;
    switch (direction) {
        case north:
            System.out.println("You are heading north");
            break;
        default:
            System.out.println("You are lost!");
    }
}

À vous de jouer !

 

Prêt à coder ? Pour accéder à l’exercice, suivez ce lien.

En résumé

  • Les conditions vous permettent d'exécuter un bloc de code uniquement si une valeur   boolean  , une variable ou une expression est évaluée comme   true  .

  • Un moyen de fournir des valeurs à un programme est d'envoyer des arguments sur la ligne de commande. Ces arguments sont mis à la disposition de la fonction main dans le tableau   args  .

  • Les expressions conditionnelles utilisent l'arithmétique booléenne, notamment les opérateurs de comparaison et les opérateurs logiques.

  • Vous pouvez évaluer plusieurs conditions en créant des chaînes d'instructions if/else if/else.

  • L'instruction switch est une façon plus propre d'exécuter en fonction des différentes valeurs possibles d'une condition particulière.

  • Les énumérations vous permettent de définir un ensemble de valeurs possibles pour rendre votre code encore plus propre.

Programmez en orienté objet avec Java

Définissez les objets et leurs attributs avec des classes

Dans la partie précédente, vous avez appris les bases de la programmation avec le langage Java. Dans cette partie, nous allons plus loin avec la programmation orientée objet (dite POO) avec Java.

Découvrez la Programmation Orientée Objet

Vous avez peut-être déjà entendu le terme "objet" dans un contexte de programmation. Mais qu'est-ce que cela veut dire exactement ? Commençons par regarder des objets du monde réel, comme des stylos, des livres, des Smartphones, des ordinateurs, etc.

Chaque type d’objet se présente sous différentes formes, mais vous pouvez les ranger dans des catégories. Si vous voulez acheter une chaise, vous vous rendez dans un magasin de meubles, au rayon correspondant, et vous avez le choix entre plusieurs modèles. Elles seront parfois très différentes les unes des autres, mais elles n’en seront pas moins des chaises.

Vous reconnaissez ces différents objets comme faisant partie du même groupe ou type. Vous remarquez des points communs entre eux, recueillez les informations et créez une représentation mentale de cette catégorie d'objets donnée.

Un autre exemple : il existe différents types de livres, mais ils ont tous tendance à avoir un titre, un auteur, une couverture, des pages, etc. En d'autres termes, les objets livres ont tous des attributs similaires qui vous permettent de les classer dans votre esprit comme faisant partie de la catégorie « livre ».

Cette liste d'attributs que nous venons de décrire pour un livre agit comme une sorte de plan pour l'objet "livre". En programmation, cela s'appelle une classe. Pour créer un livre, vous vous basez sur le plan correspondant. Bien sûr, le livre créé a un titre… Cela ne vous rappellerait pas les variables ? Après tout, en développement, tout doit avoir un nom pour pouvoir être utilisé.

Comment définir des noms de classes ?

Comme pour les variables de nommage, les noms de classes doivent être descriptifs. La principale différence, c'est qu'au lieu d'utiliser un camelCase standard, la première lettre devra également être en majuscule ; par exemple, CreationMerveilleuse et non creationMerveilleuse.

Concevez des classes

Pour savoir comment concevoir une classe, continuons avec l'exemple du livre. Nous avons déjà identifié un échantillon d'informations qui peuvent décrire un livre donné :

  • titre ;

  • auteur ;

  • nombre de pages ;

  • éditeur.

Ce sont les attributs de tout livre dans la vie réelle. Dans le contexte des classes, ces attributs sont appelés attributs de classe en langage Java. Il s’agit simplement d’un nom un peu plus pompeux pour quelque chose que vous connaissez déjà : les variables !

Maintenant que vous avez compris la théorie, passons à la pratique et mettons cela par écrit !

Pour déclarer une classe en Java, utilisez le mot-clé class suivi d'un nom personnalisé. Ensuite, terminez avec des accolades ouvrante et fermante ({}) pour l'ensemble du contenu. Ceci inclut la liste complète de ses attributs :

class Book {
// propriétés d'une classe
}

Maintenant, ajoutons les champs définis précédemment :

class Book {

    String title;

    String author;

    int numberOfPages;

    String publisher="OC";

}

Avez-vous remarqué que les trois premiers n'ont pas de valeurs, mais que le dernier en a une ?

L'exemple précédent peut s'appliquer si vous êtes un éditeur et que vous voulez cataloguer vos livres. Comme ce sont vos propres stocks, vous savez que la valeur de l'éditeur sera toujours la même, peu importe le livre. Les titres des livres, les auteurs et les numéros de page, cependant, changeront en fonction du livre en question.

Cependant, si vous revenez à l'exemple original d'une librairie en ligne, la classe ressemblerait davantage à ceci :

class Book {

    String title;

    String author;

    int numberOfPages;

    String publisher;

}

Comme vous aurez des livres de plusieurs éditeurs différents, vous ne pouvez pas mettre pour chacun d'entre eux une valeur par défaut. Vous définissez donc ici le champ, et vous ajouterez une valeur personnalisée ultérieurement !

Utilisez des classes

Vous avez un tout nouveau type –  Book  📖 – mis en œuvre !

Que pouvez-vous en faire ?

Les classes représentent le concept, ou type, de nos objets, ici Book.

Mais lorsque vous effectuez une recherche, pour un livre par exemple, vous ne tapez pas seulement « livre », n'est-ce pas ? Ce n'est pas vraiment utile ! Vous avez besoin d'une instance spécifique d'un livre, par exemple Alice au pays des merveilles. Vous cherchez un objet réel que vous pouvez feuilleter et lire. C'est la même chose en programmation informatique.

Pour utiliser une classe, vous devez créer un objet concret de cette classe. En d'autres termes, vous avez besoin d'un objet spécifique, comme un livre en particulier (Alice au pays des merveilles). Ce livre spécifique est une instance de classe ! Comme son nom l'indique, le processus est appelé instanciation ou initialisation d'un objet. Pour cela, vous créez un objet du type de la classe.

Créez des instances de classe

En Java, chaque champ de l'objet créé doit avoir une valeur. Ces valeurs peuvent être fournies de plusieurs façons. Vous l'avez vu avec l'exemple de l'éditeur : nous avions déclaré la valeur   OC  dans la définition de la classe.

Une autre manière de procéder consiste à fournir une valeur dans l'affectation qui crée la classe, c’est le constructeur. Il permet à la fois de créer une instance de la classe et de spécifier la valeur des attributs de notre nouvel objet, pratique, non ?

Ajoutons maintenant un constructeur à notre classe Book :

class Book {
      String title;
      String author;
      int numberOfPages;
      String publisher;
      //Constructeur de la classe Book
      Book(String title, String author, int numberOfPages, String publisher) {
            //Initialise l’attribut title avec la valeur de l’argument title
            this.title = title;
            this.author = author;
            this.numberOfPages = numberOfPages;
            this.publisher = publisher;
      }
}

En Java, le constructeur est une fonction spéciale du même nom que la classe avec les arguments passés en paramètres. À l’intérieur de la fonction, nous utilisons les paramètres pour initialiser les attributs de notre objet avec le mot clé this.

Il est également possible de déclarer plusieurs constructeurs différents pour la même classe. Ceux-ci peuvent même s’appeler entre eux ! Voyons cela ensemble avec notre classe Book :

class Book {
    
      String title;
    
      String author;
    
      int numberOfPages;
    
      String publisher;
    
      //Constructeur secondaire de la classe Book
      Book(String title, String author, int numberOfPages) {
            
            //Utilise le constructeur principal avec des valeur prédéfinies
            this(title, author, numberOfPages, "OC");
      }        
      
      //Constructeur principal de la classe Book
      Book(String title, String author, int numberOfPages, String publisher) {
            
            this.title = title;
            
            this.author = author;
            
            this.numberOfPages = numberOfPages;
            
            this.publisher = publisher;
      }
}

Maintenant que notre classe Book possède au moins un constructeur, voici un exemple de code pour créer un livre :

Cette variable est déclarée et initialisée avec l'expression de création de l'objet   new Book(
Déclaration et initialisation de la variable avec l'objet

Comme vous pouvez le voir, il y a quelques éléments différents. Tout d'abord, nous faisons une déclaration classique d'une variable, avec son nom   myBook  et son type   Book  . En d’autres termes, nous disons à notre programme de créer un objet appelé myBook qui est de type Book.

Vous vous souvenez, quand on disait qu'une classe est un type complexe avec un nom ? Eh bien, en voilà la preuve ! Au lieu de   int  ,   double  ou  String  , le type ici est la classe que nous avons créée ! 

Maintenant, voici le plus cool. Cette variable est déclarée et initialisée avec l'expression de création de l'objet   new Book("Coding is art","Becky James",425);  . Cette expression est composée du mot clé   new  , suivi du constructeur (  Book  ), et de valeurs à l'intérieur des parenthèses. Comme vous pouvez le voir, les parenthèses contiennent une valeur spécifiée pour chacun des champs originaux :   title  ,   author  et   numberOfPages  . 

Eh bien, voilà beaucoup de concepts et de vocabulaire nouveaux à assimiler ! Avant de continuer, récapitulons avec un schéma rapide :

Les attributs (aussi appelés champs) en Java sont les variables que vous définissez quand vous créez une classe. Pour instancier un objet, vous déclarez une variable de cette classe. Quand vous instanciez un objet de cette classe, vous définissez ég
Schéma récapitulatif

C'est mieux comme ça ? Souvenez-vous :

  • utilisez une classe comme modèle pour vos futurs objets. Dans une classe, définissez le nom et le type de certaines variables. En Java, ceux-ci sont généralement appelés attributs et champs ;

  • pour utiliser le plan vraiment cool que vous avez créé, vous devez créer un objet en utilisant le processus d'instanciation. Cela signifie déclarer une variable avec votre classe comme type, puis utiliser l'expression de création d'objet que vous avez vue ci-dessus ;

  • un objet s'appelle une instance de classe.

Maintenant, vous avez tout ce dont vous avez besoin ! Vous avez votre objet "livre", mais imaginons que vous changiez d'avis sur la valeur de vos variables. Comment accéder aux champs associés à votre nouvel objet flambant neuf ?

Une façon courante d'accéder aux champs dans de nombreux langages de programmation consiste à utiliser le point(  .  ). Cela signifie que vous devez écrire le nom d'une instance ou d'un objet suivi d'un nom d'attribut d'intérêt, séparés par un point :   instanceVariableName.attributeName  .

Exemple : Je cible l’attribut titre de cet objet book :

myBook.title = "Coding is Art"

myBook.author = "Becky James";

myBook.numberOfPages = myBook.numberOfPages + 10;

Vous pouvez maintenant modifier la valeur des champs à l'intérieur de votre objet ! Imaginez que vous vouliez ajouter dix pages parce que vous avez oublié de prendre en compte l'index du livre. Vous pouvez soit taper le nouveau numéro directement, soit ajouter dix pages à la valeur existante comme dans la troisième ligne. C'est très pratique pour faire de petits changements. 🙂

En résumé

  • Une classe est le plan d'un objet.

  • Une classe vous permet de créer des types complexes en regroupant ses attributs, en définissant des champs.

  • Pour créer un objet, vous devez déclarer une variable d'une classe et l'instancier. Utiliser le point (.) donne accès aux champs.

Passons au chapitre suivant pour aborder deux notions très puissantes en programmation orientée objet : l'héritage et le polymorphisme.

Spécialisez vos classes avec l’héritage et le polymorphisme

Dans le chapitre précédent, nous nous sommes intéressés à la notion de type complexe, et vous avez écrit et instancié votre première classe ! Dans ce chapitre, nous aborderons des concepts très importants de la programmation orientée objet en spécialisant nos classes.

Spécialisez une classe grâce à l’héritage

Reprenons notre exemple d’un livre défini par un titre et un auteur. Quand on y pense, un CD musical n’a-t-il pas également un titre et un auteur ? Ce socle commun peut être mis dans une classe média, en généralisant. Bien sûr, un livre possède des éléments particuliers (nombre de pages, par exemple), et un CD également (durée). 

Dans l’exemple ci-dessous, nous avons une classe mère   FigureGeometrique  que nous allons spécialiser en   Carre  . Le mot clé est extends. On peut dire que la classe  Carre  étend la classe  FigureGeometrique  .

Commençons par définir la classe mère  FigureGeometrique  :

public class FigureGeometrique {
    private int x;
    private int y;
    public void moveTo(int newX, int newY) {
        this.x = newX;
        this.y = newY;
    }
}

Ensuite, nous créons une classe fille  Carre  :

public class Carre extends FigureGeometrique {
    private long cote;
    public long getCote() {
        return cote;
    }
    public long getPerimetre(){
        return 4*cote;
    }
}

Avec la classe   Carre,  nous récupérons automatiquement tous les attributs de la classe de mère   FigureGeometrique  . Et nous lui avons ajouté un nouvel attribut de classe et 2 nouvelles méthodes participant ainsi à la spécialisation.

Notons également que lorsqu’on fait de l’héritage, tous les champs sont hérités. Ils peuvent être manipulés si leur accessibilité le permet (nous avons abordé ce concept dans le chapitre 5 de la première partie).

Dans notre exemple, nous pourrions créer une classe intermédiaire nommée  FigureGeometriqueAvec4Cotes  et définir notre héritage de cette manière :

  • FigureGeometrique → FigureGeometriqueAvec4Cotes → Carre ;

  • FigureGeometrique → FigureGeometriqueAvec4Cotes ​​→ Rectangle ;

  • FigureGeometrique → FigureGeometriqueAvec4Cotes ​​→ Losange ;

À quoi correspondent les objets de la classe dérivée ?

Tout objet d'une classe dérivée est considéré comme étant avant tout un objet de la classe de base :

  • un   Carre  est avant tout une  FigureGeometrique  ;

  • tout objet d'une classe dérivée cumule les  champs dérivés dans la classe de base avec ceux définis dans sa propre classe :

public class Test {
    public static void main(String[] args) {
        FigureGeo figure = new FigureGeometrique();
        figure.moveTo(1, 1);
        Carre carre = new Carre();
        carre.moveTo(2, 2);
    }
}
Initialisez les attributs hérités

L’initialisation des attributs d’une instance de classe se fait dans le constructeur de la classe, vous vous souvenez ? Lorsque des attributs sont hérités d’une classe mère, il est tout à fait possible de les initialiser dans le constructeur de la classe fille en appelant le constructeur de la classe parent. On voit ça ensemble ?

Commençons par créer un constructeur dans la classe mère  FigureGeometrique  :

class FigureGeometrique {
    private int x;
    private int y;
    FigureGeometrique(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Ensuite, utilisons celui-ci dans notre classe fille Carre :

class Carre extends FigureGeometrique {
  long cote;
   Carre(long cote, int x, int y){
     //Appel du constructeur de la classe mère FigureGeometrique
     super(x, y);
     this.cote = cote;
  }
}

En Java, pour appeler le constructeur de la classe mère depuis le constructeur de notre classe fille, nous utilisons la méthode super. Celle-ci fait directement référence à la méthode écrite dans la classe parent, ici le constructeur, mais nous verrons ça plus en détail avec le polymorphisme !

Redéfinissez une méthode de classe grâce au polymorphisme

Lorsque vous construisez une classe héritant d'une autre classe, vous avez la possibilité de redéfinir certaines méthodes de la classe mère. Il s'agit de remplacer le comportement de la fonction qui a été définie par la classe mère.

C’est le concept de polymorphisme. L’idée étant de pouvoir utiliser le même nom de méthode sur des objets différents. Et bien sûr, cela n’a de sens que si le comportement des méthodes est différent.

Redéfinissez une méthode de la classe parente

Considérons le code ci-dessous, considérons la méthode   deplacer()  dans la classe mère   Animal  :

class Animal {
    void deplacer() {
    System.out.println("Je me déplace");
}

Appliquons le principe de polymorphisme pour cette méthode dans les différentes classes filles   Chien  ,   Oiseau  et   Pigeon  :

class Chien extends Animal {

   void deplacer() {
      System.out.println("Je marche");
   }

}
class Oiseau extends Animal {

   void deplacer(){
      System.out.println("Je vole");
   }

}
class Pigeon extends Oiseau {

   void deplacer() {
      System.out.println("Je vole surtout en ville");
   }

}

Sur toutes ces classes, vous pouvez donc appeler   deplacer()  . Le polymorphisme permet alors d'appeler la méthode adéquate selon le type d'objet :

public class Test {
public static void main(String[] args) {

    Animal a1 = new Animal();

    Animal a2 = new Chien();

    Animal a3 = new Pigeon();


    a1.deplacer();

    a2.deplacer();

    a3.deplacer();

    }

}

Même si le type de nos 3 variables sont les mêmes (Animal), les instances sont différentes et donc leurs comportements aussi. Dans notre exemple, l'exécution donne comme résultat :

Je me déplace 
Je marche
Je vole surtout en ville
Appelez une méthode de la classe parente

La redéfinition des méthodes dans la classe fille remplace tout le code de la méthode mère. Parfois ce fonctionnement est idéal, parfois nous souhaitons quand même appeler le code de la classe mère tout en ajoutant autre chose dans la classe fille.

Dans notre exemple, imaginons que le chien aboie lorsqu’il se déplace.

Nous pouvons accéder à l’implémentation parente grâce au mot clé super, et appeler la méthode déplacer avant d’ajouter l’aboiement du chien :

class Chien extends Animal {
   void deplacer() {
      super.deplacer();
      System.out.println("ouaf ouaf");
   }

Attention, dans le cas d’un héritage multiple, il est seulement possible d'accéder à l’implémentation de la classe parente, pas plus ! Dans notre exemple, si nous créons une nouvelle classe fille Caniche qui étend de Chien, nous n’accéderons dans celle-ci avec super qu’à l’implémentation de la classe Chien et non de la classe Animal.

Utilisez les annotations

Il existe en Java des types spéciaux commençant par@, appelés annotations. Ils servent à préciser le comportement d’une classe, d’une méthode, d’un attribut ou même d’une variable. Les annotations donnent des informations au compilateur pour l'exécution du code de notre programme.

En Java, l'une des annotations les plus connues et utilisées est @Override. Elle est utilisée en complément du polymorphisme pour indiquer que la méthode annotée est une redéfinition d’une méthode de la classe mère. Si l’annotation est présente sur une méthode, le compilateur va vérifier que la signature de la méthode est bien identique à celle de la méthode dans la classe mère.

Reprenons une nouvelle fois notre exemple et voyons comment utiliser l'annotation  @Override  :

class Animal {
   void deplacer() {
   System.out.println("Je me déplace");
}
class Chien extends Animal {
   @Override
   void deplacer() {
      System.out.println("Je marche");
   }
}

En résumé

  • L’héritage est un concept fondamental en Java qui permet de réutiliser du code de la classe mère.

  • Le polymorphisme permet de "surcharger" les méthodes de la classe mère pour redéfinir leurs comportements sans changer leur signature.

Dans le prochain chapitre, nous verrons comment stocker et manipuler beaucoup de données avec les collections. Nous verrons qu’il existe beaucoup de types de collections différents pour classer les éléments.

Gérez les piles de données avec la bonne collection

Imaginez que vous êtes le responsable de la communication d'un théâtre à la mode. Votre responsabilité principale consiste à gérer les premiers rangs, "Front Rows", c'est-à-dire à veiller à ce que les parents et amis invités ("Guests") par les artistes, ainsi que les autres VIP, obtiennent les meilleures places pour le spectacle.

Si tout ce que vous aviez à faire consistait à gérer deux invitations pour l'artiste principal, vous pourriez imaginer utiliser seulement deux variables de type String contenant le nom de l'invité. Le code Java ressemblerait à cela :

// Déclarez une variable pour chaque invité de la première rangée
String frontRowGuest1;
String frontRowGuest2;

Ensuite, lorsque l'artiste vous fournirait les informations, vous n'auriez qu'à attribuer un nom à chaque variable. Par exemple :

// Attribuez le nom des invités
frontRowGuest1="Mary Poppins";
frontRowGuest2="Jane Banks"

Si le premier rang compte 30 sièges, cela obligera à avoir 30 variables déclarées une par une. Ne serait-il pas plus facile d'utiliser une seule variable qui contiendrait toutes ces informations ?

Vous avez de la chance ! Java offre une structure de données capable de contenir un nombre fixe de valeurs d'un même type. Cette structure s'appelle un tableau (ou Array, en anglais). Voyons quand et comment en utiliser un.

Utilisez un tableau pour stocker un nombre fixe d'éléments

Un tableau est une liste ordonnée et numérotée d'éléments du même type (par exemple, un tableau de int ne contiendra que des int !). Chaque élément est associé à un numéro appelé index. L'indexation commence par 0 (pas 1 !), ce qui signifie que le premier élément est associé à un index 0, le deuxième à 1, etc.

Déclarer un tableau utilise la même syntaxe que pour n'importe quelle variable. Par exemple, pour stocker le nombre de tasses de café que vous buvez chaque jour de la semaine, vous pouvez déclarer un tableau d'entiers avec la syntaxe suivante :

// Déclarez la variable
int[] cupsOfCoffeePerDayOfTheWeek;

Vous fournissez :

  1. Le type des éléments que le tableau contiendra, suivi de   []  .

  2. Le nom de la variable qui doit expliciter clairement l'intention du tableau.

Instanciez ensuite le tableau avec sept emplacements (les sept jours de la semaine) :

// Créez le tableau et assignez-le à la variable
cupsOfCoffeePerDayOfTheWeek=new int[7];

Lorsque le tableau est créé, chaque élément est initialisé avec la valeur par défaut du type du tableau. Dans le cas d'un tableau de   int  , cela signifie 0.

Vous pouvez aussi déclarer et créer un tableau en une seule ligne. Voici ce que vous feriez pour déclarer une variable et l'initialiser directement avec un nouveau tableau de trois   int  :

Pour déclarer un tableau, on commence par le type (int) puis des crochets ouvrant et fermant, puis le nom du tableau suivi du signe égale (=). Ensuite on l'initialise avec le mot-clé et la valeur entre crochets. On n'oublie pas le point-virgule à la f
Déclaration d'un tableau

Pour en revenir à notre gestion hebdomadaire du café : maintenant que le tableau est créé, vous pouvez y effectuer deux opérations :

  1. Accéder à une valeur à un index donné.

  2. Définir une nouvelle valeur à un index donné.

Dans les deux cas, vous utilisez le nom de la variable suivi de   [  , la valeur de l'index, et   ]  .

Par exemple, si vous buvez trois cafés le cinquième jour de la semaine, vous pouvez écrire :

// Attribuez la valeur 3 au cinquième jour de la semaine
// C'est l'index 4, puisque le premier index est 0
cupsOfCoffeePerDayOfTheWeek[4]=3;

Pour afficher le nombre de cafés que vous buvez le premier jour de la semaine, vous pouvez écrire l'instruction suivante :

//Afficher le nombre de cafés le premier jour de la semaine
System.out.println(cupsOfCoffeePerDayOfTheWeek[0]);

Enfin, si vous souhaitez définir toutes les valeurs en même temps, vous pouvez également :

  • soit définir toutes les valeurs au moment de la création du tableau ;

  • soit utiliser une boucle qui définit chaque valeur, une par une.

Pour l'instant, créons un nouveau tableau et assignons-le à la variable  cupsOfCoffeePerDayOfTheWeek  :

//Créez un nouveau tableau avec toutes les valeurs et assignez-le à notre variable
cupsOfCoffeePerDayOfTheWeek=new int[]{6,2,3,7,3,4,1};

À vous de jouer !

Et le premier rang de notre salle de théâtre ? Eh bien, vous n'avez qu'à créer un tableau de chaînes avec le nombre de sièges que contient votre premier rang. Vous pouvez soit ajouter tous vos invités au moment de la création, soit les ajouter individuellement en utilisant la syntaxe   []  .

Essayez les tableaux avec l'exercice interactif suivant :

Pour accéder à l’exercice, suivez ce lien.

Si vous voulez aller plus loin et gérer tous les rangs de votre salle de spectacle, vous pouvez utiliser des tableaux multidimensionnels. Imaginons que votre théâtre contienne 30 rangs de 12 places. Voici comment créer un tableau et attribuer une valeur au sixième siège du dixième rang : 

// Créez un tableau multidimensionnel pour gérer tous les rangs d'un théâtre
String[][] myTheatreSeats=new String[30][12];
// Rang 10, siège 6. N'oubliez pas que l'index commence à 0!
myTheatreSeats[9][5]="James Logan";

Les tableaux sont efficaces et parfaits pour gérer un nombre fixe d'éléments. Dans la pratique, vous devez souvent gérer un nombre variable d'éléments. Java facilite le traitement de ces cas avec les collections.

Commençons par la collection la plus populaire d'entre toutes, la liste.

Utilisez des listes si le nombre d'éléments n'est pas fixe

Les tableaux sont bien pratiques, mais ils ont leurs limites :

  • ils ont une taille fixe ;

  • vous ne pouvez modifier que les valeurs existantes.

Imaginons que vous vouliez classer des animaux du plus mignon au moins mignon, et que vous commenciez par un petit tableau composé de quatre éléments : renard, hibou, koala et loutre. Disons que vous voulez ajouter un écureuil et le mettre entre le renard et le hibou.

Avec un tableau, vous ne pouvez pas insérer un élément supplémentaire, vous pouvez seulement remplacer des éléments. Vous ne pouvez pas non plus ajouter votre écureuil à la fin de la liste (en programmation, on appelle cela : append). Pour cela, il faudrait créer un tout nouveau tableau de cinq cases au lieu de quatre. Puis, vous devrez à nouveau ajouter tous vos éléments à la main. Ça a l'air plutôt long et ennuyeux, non ?

C'est là qu'une liste ordonnée entre en jeu ! Comme elles sont modifiables, vous pouvez modifier le contenu et le nombre d'éléments d'une collection. Après les restrictions des tableaux, cela peut sembler magique ! 

Un petit exemple pour voir ce dont on parle :

List<String> myList = new ArrayList<String>();

Entre autres choses, avec les listes, vous pouvez :

  • accéder à chaque élément via son index ;

  • ajouter (append) un nouvel élément à la fin ;

  • insérer un nouvel élément à un index spécifique ;

  • supprimer un nouvel élément à un index spécifique.

Répétons-le, il s'agit de changer la valeur, pas le type. Le type ne peut pas être changé, même avec la puissance impressionnante d'une   ArrayList  !

Très utile ! En Java, il existe plusieurs classes qui utilisent des listes, la plus utilisée étant l’ArrayList. Vous pouvez même créer la vôtre ! L'important est que la classe que vous sélectionnez utilise l'interface   List  . 

Une interface est un contrat qui définit toutes les opérations qu'une classe doit fournir. Dans le cas de l'interface   List  , vous pouvez vérifier toutes les opérations prises en charge et la manière d'utiliser les classes sur le site web officiel des tutoriels Java (ressource en anglais).

Dans la suite de ce chapitre, nous utiliserons la classe  ArrayList  . Voyons comment cela fonctionne en pratique !

Créez une liste et ajoutez des éléments

Pour créer une liste, vous devez :

  1. Déclarer une variable dont le type est l'interface   List  . Cela signifie que vous pouvez assigner n'importe quel objet à la variable qui met en place l'interface  List,  y compris la classe   ArrayList  .

  2. Initialiser la variable avec une expression commençant par le mot clé   new  qui crée une instance de la classe   ArrayList  .

Ceci peut être fait en une seule ligne avec la syntaxe suivante :

La déclaration a lieu avant l'opérateur d'affectation  =  . Tout d'abord, vous avez le type, qui est
Création d'une liste
  1. La déclaration a lieu avant l'opérateur d'affectation  =  . Tout d'abord, vous avez le type, qui est  List  . Ceci est directement suivi de   <Integer>  , qui est le paramètre de type pour la liste. Un paramètre de type limite le type d'objets qui peuvent être stockés dans la liste. Dans cet exemple, vous ne pourrez enregistrer que des nombres entiers.

  2. La création en tant que telle a lieu avec l'expression  new  ArrayList<Integer>()  . L'objet initialisé est assigné à la variable   myList  .

Vous avez peut-être remarqué que le mot   Integer  est écrit en toutes lettres et avec une majuscule. La raison à cela : une liste ne peut stocker que des objets, pas des types primitifs. De la même manière, vous devrez utiliser :

  • Double  au lieu de   double  si vous voulez stocker les décimales ;

  • Boolean  au lieu de   boolean  si vous voulez enregistrer des valeurs vraies/fausses ;

  • Float  au lieu de   float  si vous insistez vraiment pour les utiliser.

La version objet des types primitifs est très utile car équipée de méthodes. N’hésitez pas à consulter la documentation officielle (notamment pour les integer : https://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html).

Mais qu'en est-il des choses que nous voulons mettre dans notre liste ?

Très bonne question ! Vous ne pouvez créer qu'une liste vide en Java. Pour y mettre des éléments, vous devez les ajouter un par un, comme ceci :

List<Integer> myList = new ArrayList<Integer>();
myList.add(7);
myList.add(5); //-> [7,5]
  1. La première affectation crée une liste vide appelée   myList  .

  2. Vous ajoutez ensuite un premier élément avec l'affectation   mylist.add(7)  . Java place automatiquement la valeur dans un objet  Integer  et l'ajoute à la liste à l'index 0.

  3. Enfin, l'affectation   myList.add(5)  crée une instance de la classe   Integer  avec une valeur de 5 et l'ajoute à la liste à l'index 1.

Maintenant, qu'est-ce qui se passe avec ce   .add()  ? En Java, ajouter, modifier ou supprimer des éléments nécessite l'utilisation d'une méthode, et vous l'avez deviné,   .add()  en est une ! À ce stade, tout ce que vous avez besoin de savoir sur les méthodes, c'est qu'elles vous permettent de faire des choses. 😎

Pour l'interface   List  , il y a trois méthodes implémentées par la classe   ArrayList  qui sont très pratiques :

  • add  – pour ajouter un nouvel élément à la fin d'un tableau. Ici, vous devez fournir un nouvel élément. Vous pouvez également insérer un nouvel élément à une position donnée en spécifiant l'index avant la valeur. Par exemple,   myList.add(1,12)  insère un entier avec la valeur 12 sur la position 1, et déplace la valeur existante sur la position 2, et ainsi de suite ;

  • set  – pour remplacer un nouvel élément sur un index spécifique. Ici, vous devez fournir un nouvel élément et l'index sur lequel vous voulez qu'une nouvelle valeur soit positionnée ;

  • remove  – pour supprimer un élément existant sur un index spécifique. Ici, vous devez fournir l'index de l'élément que vous souhaitez supprimer. Si vous n'avez plus besoin du premier élément, vous pouvez le retirer de la position 0. Cela déplacera le deuxième élément original de la position 1 à la position 0, le troisième de la position 2 à la position 1, et ainsi de suite.

Maintenant, à quoi est-ce que cela ressemble ? Voici la syntaxe Java pour ce qui précède :

List<Integer> myList = new ArrayList<Integer>(); // -> []
myList.add(7); // -> [7]
myList.add(5); //-> [7, 5]
myList.add(1,12) //-> [7, 12, 5]
myList.set(0,4); // -> [4, 12, 5]
myList.remove(1); // removed 12 -> [4, 5]

Décomposons cela un petit peu, d'accord ?

  • Vous pouvez voir la même opération que précédemment : ajouter 7 puis 5 à la liste.

  • Puis vous insérez 12 à l'index 1. La valeur existante de l'index 1 est déplacée vers l'index 2.

  • Ensuite, avec   .set()  , le premier nombre fait référence à l'index, et le second donne la valeur que vous voulez y mettre. En d'autres termes, vous demandez à votre   List  de modifier la valeur de l'index 0. Ceci transformera la valeur d'origine, 7, en nouvelle valeur, 4.

  • Pour finir, avec   .remove()  , vous demandez à votre liste de supprimer toute valeur trouvée à l'index 1. Cela veut dire que vous lui demandez de supprimer 12.

Ceci vous laisse avec votre liste finale contenant deux entiers : 4 et 5. Imaginez faire cela avec des tableaux de taille fixe !

Assurez le suivi du nombre d'éléments dans votre liste

Il y a une méthode très importante à utiliser avec une liste,   size()  , qui vous permet d'obtenir le nombre d'éléments dans une liste :

List<Integer> myList = new ArrayList<Integer>();
myList.add(1); // ->[1]
myList.add(2); //-> [1,2]
System.out.println(myList.size()); // -> 2

Ceci facilite grandement le suivi de l'emplacement des éléments de votre liste.

En bonus : vous pouvez aussi obtenir ces informations avec les tableaux ! Cependant, vous devrez utiliser la propriété   myArray.length  , au lieu de la méthode   myList.size()  que vous utilisez avec une liste.

La méthode   size()  est largement utilisée, notamment lorsque vous avez besoin de faire une boucle sur une liste (nous avons abordé les boucles dans le chapitre 6 de la partie 1).

Utilisez une collection non ordonnée – ensembles

Un ensemble (ou « set ») est une collection d'éléments uniques non ordonnés. Vous pouvez les utiliser si vous ne vous souciez pas de l’ordre de vos éléments – comme une liste d'ingrédients pour une recette par exemple.

Déclarez des ensembles

Comme pour les listes, Java a différentes classes pour gérer les ensembles. Nous allons nous concentrer sur l'ensemble le plus communément utilisé, le   HashSet  .

Regardons le code Java :

C'est comparable avec le fonctionnement d'une   ArrayList  : vous déclarez d'abord la variable avec l'interface
Déclaration d'un ensemble

Comme vous pouvez le voir, c'est assez comparable avec le fonctionnement d'une   ArrayList  . Vous déclarez d'abord la variable avec l'interface   Set  , et vous l'initialisez avec une instance de la classe concrète   HashSet  .

Manipulez les éléments d'un ensemble

Voici les opérations fréquentes que vous pouvez utiliser avec les ensembles :

  • ajouter un nouvel élément avec une nouvelle clé ;

  • supprimer un élément pour une clé spécifique ;

  • compter le nombre d'éléments de l'ensemble.

Et l'accès à un  élément  ?

Comme les ensembles sont des collections non ordonnées, il n'y a pas de moyen simple de pointer des éléments particuliers – du moins pas comme ceux que vous avez vus jusqu'ici. Il y a des façons de le faire, mais vous en apprendrez davantage à ce sujet au fur et à mesure que vous approfondirez vos connaissances en programmation.

Pour ajouter un nouvel élément, utilisez   add()  :

Set<String> ingredients = new HashSet<String>();
ingredients.add("eggs");
ingredients.add("sugar");
ingredients.add("butter");
ingredients.add("salt");

Comme les ensembles sont non ordonnés, il n'est pas nécessaire d'utiliser « append » ou d'insérer des éléments à un point spécifique. Il vous suffit d'utiliser   add()  !

Pour supprimer un élément, vous utilisez   remove()  :

Set<String> ingredients = new HashSet<>();
ingredients.add("salt"); //ajoutez du sel sur les ingrédients
ingredients.remove("salt"); //enlevez du sel des ingrédients

L'important, c'est de fournir l'élément exact à supprimer. Ici, vous vous débarrassez du « sel ».

Les ensembles, comme les listes, utilisent également la méthode   size()  pour obtenir le nombre d'éléments dans un ensemble :

System.out.println(ingredients.size());

Consultez les dictionnaires ou "maps"

Par exemple, si vous voulez lister les noms de vos amis et leur âge respectif, les dictionnaires sont la bonne collection à utiliser :

Éléments du dictionnaire présentant une liste de noms d'amis et leur âge respectif.
Éléments du dictionnaire

Toutes les clés d'un dictionnaire doivent être uniques, tout comme un numéro de plaque d'immatriculation de voiture est unique.

Déclarez des dictionnaires

Pour cette section, nous utiliserons la classe la plus courante,   HashMap  . Voici le code pour déclarer et initialiser une instance de cette classe :

La différence principale, en comparaison avec les listes et les ensembles, c'est qu'il faut paramétrer le dictionnaire avec deux éléments, en utilisant la syntaxe    .
Déclaration d'un dictionnaire

La différence principale, en comparaison avec les listes et les ensembles, réside dans le fait que vous paramétrez votre dictionnaire avec deux éléments, en utilisant la syntaxe  <String, Integer>  .

  1. String  est le type de la clé.

  2. Integer  est le type de la valeur.

Pour ajouter des éléments à votre dictionnaire, utilisez la méthode   myMap.put()  . Entre parenthèses, indiquez d'abord la clé, puis la valeur :

// Les âges de mes amis
myMap.put("Jenny", 34);
myMap.put("Livia", 28);
myMap.put("Paul", 31);
System.out.println(myMap.get("Jenny")); // -> 34

Dans la dernière ligne, nous avons demandé au programme d'afficher la valeur de la clé, Jenny. Dans ce code :

  • la méthode   System.out.println()  affiche le résultat d'une expression fournie entre parenthèses ;

  • l'expression réelle,   myMap.get("Jenny")  , renvoie la valeur identifiée par la touche "Jenny", qui est stockée dans la variable myMap.

Lorsque vous utilisez des chaînes comme clés, n'oubliez pas que ces clés sont sensibles à la casse. Ainsi, "Jenny" et "jenny" sont (par exemple) deux clés différentes associées à deux éléments différents dans un dictionnaire. Par exemple :

myMap.put("Jenny", 34);
myMap.put("Livia", 28);
myMap.put("Paul", 31);
myMap.put("jenny", 21);
System.out.println(myMap.get("Jenny")); // -> 34
System.out.println(myMap.get("jenny")); // -> 21

Pour éviter de telles situations, une astuce consiste à utiliser des constantes pour spécifier les clés une fois et les réutiliser ensuite dans tout le code :

// Définissez des clés en tant que constantes dans votre classe
private static final String KJENNY = "Jenny";
private static final String KLIVIA = "Livia";
private static final String KPAUL = "Paul";
// Utilisez des constantes en tant que keys
myMap.put(KJENNY, 34);
myMap.put(KLIVIA, 28);
myMap.put(KPAUL, 31);

// Accédez à un élément
System.out.println(myMap.get(KJENNY)); // -> 34

Vous voyez que vous devez utiliser ici les mots clés   private static final   pour indiquer que vous avez besoin que vos chaînes soient des constantes et non des variables. Ceci est très utile, car cela vous garantit que vous garderez vos clés pour toujours, et que vous ne perdrez aucune donnée en changeant accidentellement les chaînes de clés !

Si le type le plus couramment utilisé est   String  , les clés en Java peuvent être de différents types, par exemple   Integer  :

Map<Integer, String> myMap = new HashMap<Integer, String>();
morning.put(1, "Wake up");

Les dictionnaires ont en commun avec les types de variables et les tableaux simples, que leur type ne peut pas être modifié.

Toutes les clés doivent être du même type, et toutes les valeurs doivent être du même type.

myMap.put("Jenny", 34);
myMap.put("Livia", 28); // Ok
myMap.put("Paul", "Designer") // Error
Manipulez les éléments du dictionnaire

Voici les opérations fréquemment utilisées que vous pouvez effectuer avec les dictionnaires :

  • accéder à la valeur d'un élément par sa clé ;

  • ajouter un nouvel élément avec une nouvelle clé et une valeur ;

  • supprimer un élément pour une clé spécifique.

Imaginons un dictionnaire de noms de personnes et de leur profession, appelé « professions ».

  1. Vous pouvez ajouter une valeur avec une méthode   put()  et deux arguments : la clé et la valeur.

  2. Pour accéder à une valeur, utilisez simplement la méthode   get()  avec la clé en argument.

Allons jeter un coup d'œil à cela :

professions.put("Jenny", "Business owner");
System.out.println(professions.get("Jenny")); // -> Business owner

Vous pouvez également modifier un élément en réutilisant une clé existante avec la méthode

put()  :

professions.put("Jenny", "Business owner");
System.out.println(professions.get("Jenny")); // -> Business owner
professions.put("Jenny", "Developer");
System.out.println(professions.get("Jenny")); // -> Developer

Enfin, pour supprimer un élément, vous pouvez utiliser une méthode   remove()  avec la clé de l'élément que vous voulez supprimer :

professions.remove("Jenny");
Comptez les éléments

Les dictionnaires, comme les listes et les ensembles, ont aussi la propriété   size()  qui permet d'obtenir le nombre d'éléments du dictionnaire :

System.out.println(professions.size());

En résumé

Dans ce chapitre, vous avez appris les bases pour travailler avec des conteneurs qui stockent plusieurs éléments d'un type de données en particulier :

  • conteneurs de taille fixe : Arrays – les éléments d'un tableau sont indexés à partir de 0 et sont accessibles à l'aide de cet index. Le nombre d'éléments ne peut pas être modifié ;

  • listes ordonnées : Lists – les éléments d'une liste se comportent comme un tableau. Le nombre d'éléments peut évoluer en ajoutant et en supprimant des éléments ;

  • listes non ordonnées : Sets – les éléments d'un ensemble sont stockés sans ordre particulier. Vous pouvez y accéder en les énumérant ;

  • dictionnaires : Maps – les éléments d'un dictionnaire sont organisés par paires clé-valeur et sont accessibles à l'aide d'une clé ;

  • les actions les plus courantes effectuées avec les collections sont :

    • accéder à un élément,

    • ajouter un nouvel élément,

    • supprimer un élément,

    • modifier un élément,

    • compter tous les éléments,

    • parcourir tous les éléments.

La collection que vous choisissez dépend de la tâche à accomplir. Au fur et à mesure que vous progressez dans votre carrière, vous serez en mesure d'identifier le type le plus approprié à utiliser. Passons maintenant au passage de paramètres et des valeurs de retour. À tout de suite !

Gérez différents types de passage de paramètre

Depuis le début de ce cours, nous utilisons des fonctions (ou méthodes) comme   System.out.println()  . Dans ce chapitre, vous allez vous familiariser avec les différents composants d'une déclaration de méthode (paramètres et valeur de retour), et la différence entre un type valeur et un type référence

Comprenez l'utilité de déclarer une méthode

Supposons que vous ayez un rectangle de 6 (de longueur) par 4 (de largeur), et que vous vouliez calculer son périmètre.

public static void displayPerimeter() {
   int perimeter = 2*(4 + 6);
   System.out.println(perimeter); // -> 20
}

Cette méthode est correctement implémentée, mais avouons-le, tout à fait inutile car elle fournit toujours le même résultat… . De quoi limiter votre champ d’action ! Comment faire pour qu’elle calcule un périmètre avec d’autres données ?

Définissez des fonctions avec des paramètres

Pour remédier à cela, vous devez faire en sorte que votre fonction accepte des données extérieures. Pour cela, vous pouvez définir une fonction avec des paramètres.

En Java, le type du paramètre et son nom correspondant dans le cadre de la fonction font partie de la déclaration de fonction. Voilà à quoi cela ressemble :

public static void displayPerimeter(int length, int width) {
   int perimeter = 2 * (length + width);
   System.out.println(perimeter);
   //La valeur affichée dépend de la longueur (length) et la largeur (width)
}

Les paramètres sont des variables listées dans la déclaration de fonction et qui sont spécifiées à l'intérieur de parenthèses   ()  avec le nom et le type. Vous pouvez maintenant appeler cette fonction et lui passer les valeurs que vous voulez :

displayPerimeter(10, 11); // -> 42
displayPerimeter(2, 2); // -> 8

Chaque valeur est affectée à un paramètre dans l'ordre de leur définition.

Détaillons un peu cette nouvelle terminologie. Les paramètres sont les variables déclarées dans une fonction. Les valeurs passées sur cette fonction s'appellent des arguments.

Parfait, nous avons ajouté un but à notre fonction. Maintenant, que pouvons-nous faire avec le résultat ?

Souvent, le code qui a appelé la fonction a besoin d'une réponse pour effectuer son propre travail. Cette réponse peut être fournie par la valeur de retour de la fonction.

Définissez une valeur de retour

Pour définir une valeur de retour, vous devez :

  1. Modifier la déclaration de la fonction pour indiquer qu'il est prévu qu'elle renvoie un résultat.

  2. Terminer la fonction avec le mot clé  return  .

Voici comment transformer le   displayPerimeter  en fonction   calculatePerimeter  . Ceci retournera le résultat du calcul pour qu'il puisse être utilisé par l'appelant :

public static int sum(int a, int b)
{
   int calc = a + b;
   return calc;
}

Vous pouvez également renoncer à la variable somme, et renvoyer directement le résultat de l'expression de calcul :

public static int sum(int a, int b){
   return a + b;
}

Une fois votre fonction définie, vous pouvez l'utiliser autant de fois que vous le souhaitez. Voici quelques variantes :

int sumOfSmallNumbers = sum(3,7);
System.out.println(sumOfSmallNumbers); // -> 10
int sumOfLargerNumbers = sum(sumOfSmallNumbers,999);
System.out.println(sumOfLargerNumbers); // -> 1009

Dans l'exemple ci-dessus, nous avons utilisé le résultat du premier calcul comme paramètre pour le suivant.

Un autre élément à comprendre et à assimiler : certaines variables stockent une valeur directement, tandis que d'autres stockent une référence. Cela a un impact sur la façon dont elles sont envoyées comme arguments à une fonction.

Différenciez les type valeur et les type référence

Vous avez appris qu'il existe des types de variables simples (comme les nombres ou les chaînes) ou plus complexes (comme les classes). Chacun de ces types est classé comme un type valeur ou un type référence.

Commençons par les types valeur.

Types valeur

Prenons par exemple les nombres entiers. Si vous créez un  int  et que vous lui donnez une valeur, puis que vous affectez cette variable à une seconde variable  int  , la valeur de la première sera copiée dans la seconde :

// Déclarez la variable "a" en tant que "int" et l'initialiser avec une valeur de 10
int a = 10;
// Déclarez la variable "b" en tant que "int" et l'initialiser avec une copie de la valeur de "a"
int b = a;

Puisque   int  est un type valeur, une copie de la valeur de  a  est assignée à   b  . Les deux valeurs sont indépendantes. Cela signifie que si vous modifiez ultérieurement la valeur de a, la valeur de b ne sera pas modifiée.

System.out.println(a); // -> 10
System.out.println(b); // -> 10
a = 15;
System.out.println(a); // -> 15
System.out.println(b); // -> 10

C'est assez simple. Viennent ensuite les types référence.

Types référence

Dans cette section, nous allons travailler avec des classes. Lorsque vous créez une variable et lui affectez une instance de classe, l'objet est créé, mais la variable elle-même contient une référence à cet objet : l'emplacement où il est stocké dans la mémoire. Ensuite, lorsque vous affectez la valeur de cette variable à une autre variable, l'objet lui-même ne sera pas copié, et la nouvelle variable contiendra une référence à cet objet : la même adresse.

En voici un exemple :

public class Car {
   String color = "red";
}
Car car = new Car();
Car carToPaint = car;

Si vous voulez peindre la voiture qui est accessible par la deuxième variable, la première variable sera également affectée car les deux pointent exactement vers la même instance de la classe « car » (voiture) :

public class Car {
   String color = "red"
}
Car car = new Car();
Car carToPaint = car;
System.out.println(car.color); // -> red
System.out.println(carToPaint.color); // -> red
carToPaint.color = "yellow";
System.out.println(car.color); // -> yellow
System.out.println(carToPaint.color); // -> yellow

Vous voyez la différence ? ⚡️ C'est très utile, car cela permet d'économiser beaucoup de mémoire si vous devez faire passer le même objet sur tout votre programme. Cependant, cela peut également être dangereux, car changer un objet dans une partie d'un programme affecte toutes les autres parties du programme qui utilisent le même objet.

class Car {
   String color = "red";
}
public void paint( Car car, String color) {
    car.color = color;
}

Car car = new Car();
System.out.println(car.color); // -> red
paint(car, "green");
System.out.println(car.color); // -> green

Mais les paramètres des fonctions ne seraient-ils pas des constantes en Java ?

En effet, en Java, les paramètres d'une fonction sont des constantes et ne peuvent pas être modifiés, ce qui signifie que vous ne pouvez pas affecter une nouvelle valeur à l'ensemble du nouvel objet (ou une référence à un objet). Mais vous pouvez tout à fait modifier les attributs de cet objet. C'est ce que nous avons fait dans l'exemple ci-dessus.

  • Lors des appels de méthode, les arguments sont toujours passés par valeur. Dans le cas des types primitifs (  int  ,   char  ,   byte  , etc.), c'est la valeur de l'argument qui est recopiée dans le paramètre de la méthode.

int cote = 5;
agrandirCote(cote); // Cette méthode ajoute 2cm au côté qui lui a été passé en paramètre

Dans l’exemple ci-dessus, la variable   cote  aura toujours la valeur 5 en dehors de la fonction   agrandirCote(int leCoteAagrandir)  .

  • Dans le cas des types « objet », c'est la valeur de la variable (la référence à l'objet), qui est transmise à la méthode.

Integer cote = 5;
agrandirCote(cote); // Cette méthode ajoute 2cm au côté qui lui a été passé en paramètre

Cette fois-ci, une fois la méthode  agrandirCote(Integer leCoteAagrandir)  appelée avec la variable coté, cette variable aura pour valeur 7 en dehors de la méthode (une fois la méthode exécutée).

En résumé

  • Les fonctions peuvent avoir des paramètres et des valeurs de retour (return).

  • Une valeur de retour est le résultat de l'exécution de la fonction. Elle peut être renvoyée au bloc de code qui a appelé la fonction, et être utilisée si besoin est.

  • Les paramètres sont les entrées d'une fonction nécessaires à l'exécution et à l'obtention d'un résultat.

  • Les paramètres sont des variables définies par leur nom et par leur type. Ces paramètres sont spécifiés dans la déclaration de la fonction.

Lorsque vous appelez une fonction, vous passez des valeurs à ces variables. Ces valeurs sont appelées arguments. 

Utilisez quelques principes de programmation avancés en Java

Entrez dans les détails grâce à la récursivité

 

Comprenez le principe de récursivité

Dans ce chapitre, vous allez apprendre l'un des concepts fondamentaux de la programmation : la récursivité.

Souvenez-vous des mathématiques factorielles

Commençons par regarder la récursivité dans le contexte des mathématiques factorielles. Vous vous souvenez de ce qu'est une factorielle ?

5! = 5 x 4 x 3 x 2 x 1

4! = 4 x 3 x 2 x 1

3! = 3 x 2 x 1

En fait, la façon dont fonctionne une factorielle nous donnera un bon exemple de la récursivité dans Java. Une autre façon plus abstraite de l'écrire serait :

N! = N x (N-1)!

En utilisant des chiffres, cela donne :

5! = 5 x (5-1)!  , c'est-à-dire   5! = 5 x (4)!  , et au final   5! = 5 x 4!  .

Mais, suivant cette formule, nous pouvons également écrire   4!  comme   4 x (4-1)!  , ce qui nous donne   4 x 3!  . Nous pouvons ainsi revenir à   5! = 5 x 4!  pour obtenir :   5! = 5 x (4 x 3!)  .

Une minute ! En regardant notre nouvelle version,   3!  peut également utiliser cette formule ! Ce qui va finalement nous donner :   5! = 5 x 4 x (3 x 2!)  . Nous pouvons recommencer avec   2!  .

Même si vous n'êtes pas très doué en maths, vous pouvez voir ici que la formule factorielle est utilisée à plusieurs reprises.

Appliquez le principe en langage Java

Du point de vue du code, vous pouvez créer une fonction qui s'exécute de façon répétée, comme ici, jusqu'à ce que vous ayez une réponse complète.

Comment écrire une factorielle en langage Java en utilisant des opérateurs ?

Essayons :

// définissez des classes
class RecursionInJava {
    public static int factorial(int n) {
        n = n * factorial (n-1);
    }
}

Attention, le code ci-dessus ne fonctionne pas !  Vous pouvez tester pour voir. C'est un exemple qu'on va améliorer juste après !

Si vous regardez attentivement la ligne   n = n * factorial(n-1)  , vous constaterez que la méthode factorielle est appelée à l'intérieur d'elle-même. C'est ce qu'on appelle la récursivité, parce que la méthode fonctionne en continu en s'appelant elle-même.

Vous vous souvenez des instructions conditionnelles comme if/else ? Elles permettent à une instruction de s'appliquer à l'infini jusqu'à l'arrêt de la méthode pour une raison donnée. Pourquoi voudriez-vous arrêter cette méthode ? Lorsque vous effectuez une équation factorielle, elle se termine à 1. En transposant à la programmation, vous devez créer une instruction conditionnelle qui mettra fin à la récursion quand n sera égal à 1.

Ceci fera deux choses :

  1. Cela arrêtera la méthode factorielle quand n sera égal à 1.

  2. Si le numéro d'origine est 1, l'instruction renvoie simplement 1.

Voyons à quoi ça ressemble :

// Définissez des classes
class RecursionInJava {
// La méthode corrigée
    public static int factorial(int n) {
        if (n == 1) return 1;
        else return n * factorial (n-1);
    }
}

Revenons maintenant à l'exemple du livre. Si vous voulez compter les livres dans chaque catégorie, chaque sous-catégorie, et ainsi de suite, vous devez vous assurer que vous prenez en compte toutes les différentes couches. Puis, une fois terminée, cette fonction s'appellera elle-même, mais cette fois en fonctionnant avec les sous-catégories de l'une des catégories d'origine. Cette itération ira plus loin et s'appellera elle-même à nouveau pour traiter des sous-catégories. Et ainsi de suite, jusqu'à ce qu'il n'y ait plus de sous-catégories. De cette façon, vous pouvez aller jusqu'au tout dernier sous-niveau de chaque catégorie d'origine. Et si une catégorie n'a pas de sous-catégories, votre fonction reviendra à votre point de départ.

Voici à quoi ressemble le code qui implémente cela en Java :

class BookStore {

    public static Categories categories;

// Définissez une fonction récursive
    public static int countBooks(Categories categories) {
        int c = 0;
        for (Category category : categories) {
            c += category.numberOfBooks;
            c += countBooks(category.subCategories);
            }
        return c;
    }

    public static void main(String[] args) {
// Appelez cette fonction récursive
        Category c1 = new Category();
        c1.numberOfBooks = 3;
        Category c2 = new Category();
        c2.numberOfBooks = 2;
        Category c3 = new Category();
        c3.numberOfBooks = 10;
        Categories subc1 = new Categories();
        subc1.add(c3);
        c1.subCategories = subc1;

        categories = new Categories();
        categories.add(c1);
        categories.add(c2);

        int numberOfBooks = countBooks(BookStore.categories);
        System.out.println("The bookstore has " + numberOfBooks + " books");
    }


}
---------------
import java.util.ArrayList;

class Categories extends ArrayList<Category> {

}

---------------
public class Category {

    public int numberOfBooks;

    public Categories subCategories = new Categories();

}

Très efficace, n'est-ce pas ?

En termes techniques, la récursion est une action qui s'initialise à l'intérieur d'elle-même. Elle continue de descendre couche après couche, jusqu'à ce qu'une condition soit remplie, puis remonte jusqu'à la première couche.

La récursivité est l'un des concepts fondamentaux de tout langage de programmation ! 

En résumé

  • La récursivité est une action qui s'initialise à l'intérieur d'elle-même.

  • Les méthodes récursives servent à passer des structures en couches.

Passons au chapitre suivant pour apprendre comment les erreurs et les exceptions permettent de gérer les comportements inattendus.

Gérez les comportements inattendus

Gérez les erreurs

Les erreurs sont des problèmes de code. Elles peuvent aller de fautes de frappe à l'utilisation incorrecte de variables ou de fonctions qui peuvent être repérées immédiatement, en passant par des erreurs qui surviennent pendant l'exécution du programme. Elles dépendent souvent de la séquence d'exécution et des résultats du code précédemment exécuté.

Examinons comment créer et utiliser une application. Vous l'écrivez dans un langage de programmation. Ce code est traduit en code machine, et il est ensuite exécuté au fur et à mesure que vous utilisez une application.

Voici les deux phases de ce processus :

  • compilation (ou interprétation dans certaines langues) – c'est là que le code est vérifié et traduit en code machine. Le résultat du processus de compilation est un fichier qui est prêt à être exécuté et appelé – exécutable ;

  • exécution – exécuter l'application – exécuter le fichier exécutable !

Chacune des phases peut avoir son propre ensemble d'erreurs.

Repérez et corrigez les erreurs de compilation

Des erreurs de compilation peuvent être détectées par le compilateur Java (javac), pendant le processus de traduction de votre code Java lisible par un être humain, en code byte lisible par une machine. Ces erreurs peuvent être syntaxiques (fautes d'orthographe) ou sémantiques, telles que l'utilisation de mots interdits, de code non interprétable, de types incorrects, etc.

Si vous utilisez un environnement de développement intégré (IDE) comme Eclipse ou IntelliJ IDE, vous serez averti des erreurs en direct lorsque vous taperez votre code. Les lignes de code qui correspondent à des erreurs de compilation sont surlignées en rouge. Ce sont des erreurs dites « faciles », car elles peuvent (et doivent) être corrigées sur place. L'application ne peut pas être compilée tant qu'il y a des erreurs de compilation. Mais elles sont faciles à réparer, et vous pouvez voir exactement ce qui ne va pas.

Les erreurs d'exécution, quant à elles, peuvent être difficiles à corriger.

Gérez les erreurs d'exécution

Les erreurs d'exécution surviennent pendant le processus d'exécution, après le lancement de l'application. Elles n'apparaissent que pendant le processus d'exécution, et il n'y a aucun moyen de les détecter pendant la compilation.

Ce type d'erreur est typiquement lié à la logique du code, comme l'accès à des données qui n'existent pas ou qui existent dans un format différent, ou à l'exécution d'une action non prise en charge, etc.

Par exemple, si vous essayez d'accéder à un élément d'une liste dont l'index est supérieur à la longueur de cette liste, il y a une erreur de logique : vous pensiez que la liste serait plus longue.

Un exemple d'erreur de logique métier pourrait être que dans une application financière, vous avez inversé la signification des termes crédit et débit (ce qui n’est pas du tout OK). Cependant, l'application parviendra quand même à fonctionner dans cette situation. Elle va simplement produire tout le contraire du résultat que vous attendez ! Alors, ne les mélangez pas !

En général, une erreur logique a pour principale conséquence un plantage de l'application.

Certaines de ces erreurs peuvent facilement être reproduites au fur et à mesure qu'elles surviennent. D'autres ne se produisent qu'occasionnellement. Elles sont plus difficiles à résoudre car elles sont généralement plus difficiles à reproduire. Les erreurs occasionnelles sont habituellement liées à des conditions particulières du code. Voici un exemple de ce genre d'erreur ninja dans la structure du code :

if(weekDay) {
   //exécuter le code sans erreur
   }
else {
   //exécuter le code avec des erreurs
}

Dans l'exemple ci-dessus, si vous travaillez sur votre projet uniquement du lundi au vendredi et que vous obtenez tous les plantages signalés pendant le week-end, il ne sera pas évident au début de savoir pourquoi votre application plante.

Alors, qu'est-ce que vous pouvez y faire ?

Dans certains cas, vous pouvez déboguer votre programme et détecter ces points faibles.

Vous pouvez généralement utiliser deux stratégies :

  1. Recherche d'erreurs à l'aide de conditions.

  2. Tirer parti du mécanisme des exceptions.

Revoyons chaque stratégie avec un exemple courant : la fameuse erreur de la division par zéro.

Recherchez les erreurs

Créez d'abord une classe utilitaire dans un package   exceptions  , appelé   SimpleMaths  , qui fournit une méthode   calculateAverage  qui calcule la moyenne d'une liste de valeurs entières.

Sinon, vous pourrez vous entraîner dans l'exercice interactif à la fin de ce chapitre.

package exceptions;

import java.util.List;

public class SimpleMaths {

   /** calculez la valeur moyenne d'une liste d'entiers
   *
   * @param listOfIntegers une liste contenant des nombres entiers
   * @return la moyenne de la liste
   */

   public static int calculateAverage(List<Integer> listOfIntegers) {
      int average=0;
      for (int value: listOfIntegers) {
         average+=value;
      }
      average/=listOfIntegers.size();
      return average;
   }
}

Définissez ensuite un programme dans une classe nommée   TemperatureAverage  à l'intérieur du même paquet d'exceptions qui fait usage de cette fonctionnalité. Ce programme reçoit les valeurs de température comme arguments en ligne de commande. Il appelle la fonction   calculateAverage  et affiche le résultat.

package exceptions;

import java.util.ArrayList;
import java.util.List;

public class TemperatureAverage {

   /** affiche la température moyenne à partir des valeurs fournies comme arguments en ligne de commande
   *
   * @param args liste de températures séparées par des espaces
   */
   public static void main(String[] args) {

      List<Integer> recordedTemperaturesInDegreesCelcius = new ArrayList<Integer>();

      // remplissez la liste à partir des valeurs fournies comme arguments en ligne de commande
      for (String stringRepresentationOfTemperature : args) {
         int temperature = Integer.parseInt(stringRepresentationOfTemperature);
         recordedTemperaturesInDegreesCelcius.add(temperature);
      }

      // calculez et affichez la température moyenne
      Integer averageTemperature = SimpleMaths.calculateAverage(recordedTemperaturesInDegreesCelcius);
      System.out.println("The average temperature is " + averageTemperature);
   }

}

Dans ce code, créez d'abord une liste vide, puis itérez sur le tableau   args  contenant les arguments de ligne de commande fournis. Puisque chaque argument est fourni sous la forme d'une   String  , convertissez-le en   Integer  à l'aide de Integer.parseInt avant de l'ajouter à la liste. Une fois la liste complétée, appelez la fonction   calculateAverage  de la classe   SimpleMaths  , affectez le résultat à la variable   averageTempérature  et imprimez sa valeur.

Compilons et exécutons ce code avec différents arguments :

$ javac exceptions/*.java $ java exceptions.TemperatureAverage 4 7 9 16 The 
average temperature is 9

$ java exceptions.TemperatureAverage 4 7 The average temperature is 5

$ java exceptions.TemperatureAverage Exception in thread "main" 
java.lang.ArithmeticException: / by zero at 
exceptions.SimpleMaths.calculateAverage(SimpleMaths.java:17) at 
exceptions.TemperatureAverage.main(TemperatureAverage.java:23)

Sans surprise, vous avez des problèmes si vous ne fournissez aucun argument sur la ligne de commande, puisque vous ne pouvez pas diviser par zéro. Pour éviter cette erreur, ajoutez un contrôle dans la méthode   calculateAverage  :

if (listOfIntegers.size()==0){
   return 0;
}

Toutefois, cette stratégie produirait un résultat très trompeur :   0  n'est pas un résultat valide. Nous n'avons même pas effectué la division ! Nous pouvons, cependant, régler la fonction   main  pour éviter d'appeler la fonction avec une liste vide :

package exceptions;

import java.util.ArrayList;

import java.util.List;

public class TemperatureAverageWithCheckForEmptyList {

   /** affichez la température moyenne à partir des valeurs fournies comme arguments en ligne de commande
   *
   * @param args liste de températures séparées par des espaces
   */

   public static void main(String[] args) {

      List<Integer> recordedTemperaturesInDegreesCelcius = new ArrayList<Integer>();

      // remplissez la liste à partir des valeurs fournies comme arguments en ligne de commande
      for (String stringRepresentationOfTemperature : args) {
         int temperature = Integer.parseInt(stringRepresentationOfTemperature);
         recordedTemperaturesInDegreesCelcius.add(temperature);
      }

      // Protection contre la liste vide
      if (recordedTemperaturesInDegreesCelcius.size() == 0) {
         System.out.println("Cannot calculate average of empty list!");
      } else {
         // calculez et affichez la température moyenne
         int averageTemperature =
SimpleMaths.calculateAverage(recordedTemperaturesInDegreesCelcius);
         System.out.println("The average temperature is " + averageTemperature);
      }

   }

}

Voyons cela en action :

$ javac exceptions/*.java
$ java exceptions.TemperatureAverageWithCheckForEmptyList Cannot calculate 
average of empty list!

Génial ! Vérifions avec d'autres arguments :

$ java exceptions.TemperatureAverageWithCheckForEmptyList 8 6 9 The average 
temperature is 7
$ java exceptions.TemperatureAverageWithCheckForEmptyList 8 6 eight2 Exception 
in thread "main" java.lang.NumberFormatException: For input string: "eight" at 
java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.j
ava:65) at java.base/java.lang.Integer.parseInt(Integer.java:652) at 
java.base/java.lang.Integer.parseInt(Integer.java:770) at 
exceptions.TemperatureAverageWithCheckForEmptyList.main(TemperatureAverageWithCh
eckForEmptyList.java:18)

Il semble qu'il y ait un autre problème : notre méthode   parseInt  n'accepte pas "eight" comme entrée, et lance une exception NumberFormatException qui fait planter le programme.

Intéressant ! Nous obtenons ce qu'on appelle un suivi de la pile : un message que Java imprime lorsqu'un programme plante. Il nous dit même ce qui s'est passé :  java.lang.NumberFormatException: For input string: "eight."  .

Donc, si Java est capable d'imprimer ce message, pourrait-il empêcher le crash en tant que tel ?

Bien sûr que oui ! C'est à cela que sert le mécanisme de gestion des exceptions. 🙂

Gérez les exceptions

Le mécanisme de gestion des exceptions est le mécanisme de gestion des erreurs inclus par défaut dans Java. Il a pour fonction de faire un throw avec un événement qui interrompt le déroulement normal de l'exécution en cas de problème. Si cet événement est détecté, le problème peut être résolu. Sinon, le programme plante avec une trace de pile qui décrit ce qui s'est passé.

Pour cela, il existe un modèle de code général,  try/catch  (essayer/capturer). Cela signifie que vous demandez à faire quelque chose – exécuter un bloc de code, ou plutôt,   try  (essayer) de le faire. Si une erreur se produit, vous la capturerez :   catch  .

Vous pouvez gérer l'erreur (ou les erreurs) dans la partie catch. Cela peut être aussi simple que de ne rien faire ou de réessayer (si votre logique métier le permet). En capturant une erreur (même si vous n'avez rien fait), vous avez empêché l'application de planter.

Voici à quoi ressemble cette construction en Java :

try {
// un peu de code
// une fonction à essayer pouvant générer une erreur
// encore du code
}
catch (ExceptionName e) {
// code à exécuter au cas où l'essai ne fonctionnerait pas et qu'une erreur se produirait
}

Que se passe-t-il si on essaie avec try ?

En cas de problème à l'intérieur d'une méthode pendant son exécution, une erreur est générée,   throw  . Cette exception remonte jusqu'à la chaîne d'appels de méthode jusqu'à ce qu'elle soit capturée,   catch  . Si aucune instruction d'interception n'est fournie, le programme finit par planter.

Corrigeons notre programme pour gérer nos sources d'erreurs connues :

package exceptions;

import java.util.ArrayList;
import java.util.List;

public class TemperatureAverageWithExceptionHandling {

   /** affichez la température moyenne à partir des valeurs fournies comme arguments en ligne de commande
   *
   * @param args liste de températures séparées par des espaces
   */

   public static void main(String[] args) {

      try {
         List<Integer> recordedTemperaturesInDegreesCelcius = new ArrayList<Integer>();
         // remplissez la liste à partir des valeurs fournies comme arguments en ligne de commande
         for (String stringRepresentationOfTemperature : args) {
            int temperature = Integer.parseInt(stringRepresentationOfTemperature);
            recordedTemperaturesInDegreesCelcius.add(temperature);
         }
         // calculez et affichez la température moyenne
         int averageTemperature =
SimpleMaths.calculateAverage(recordedTemperaturesInDegreesCelcius);
         System.out.println("The average temperature is " + averageTemperature);
      } catch (NumberFormatException e) {
         System.out.println("All arguments should be provided as numbers");
         System.exit(-1);
      } catch (ArithmeticException e) {
         System.out.println("At least one temperature should be provided");
         System.exit(-1);
      }

   }

}

Ce code est divisé en deux parties :

  • le flux normal, entre   try{  et  }  , est détecté : il se compose des instructions à exécuter tant qu'aucune erreur ne se produit ;

  • Un ensemble d'instructions   catch(Exception e)  . Si une erreur se produit et que l'exception qui est lancée correspond au type d'exception défini dans une instruction   catch  , le bloc qui correspond à cette instruction catch est exécuté. Si aucune instruction de capture ne correspond, le programme plante et affiche la trace de la pile.

Ce mécanisme permet de séparer le flux normal d'un programme de la partie de traitement des erreurs. Il permet même de gérer une erreur de méthode dans la méthode appelante. Dans notre exemple, l'exception   ArithmeticException  est générée depuis la méthode   calculateAverage  , mais interceptée dans la méthode   main  .

En résumé

  • La compilation est le processus de traduction du code d'un langage de programmation en code machine. Les erreurs de compilation sont faciles à découvrir et doivent être résolues pour terminer le processus de compilation.

  • L'exécution est le processus d'utilisation ou d'exécution de l'application. Les erreurs d'exécution sont plus difficiles à découvrir et provoquent le crash d'une application.

  • En Java, les erreurs d'exécution génèrent des exceptions.

  • Les exceptions sont gérées à l'aide d'une instruction try/catch :

    • Try  indique qu'une fonction peut lancer une exception ;

    • Catch  indique quel code exécuter si une exception est générée (throw).

Dans le chapitre suivant, nous traiterons de la lecture et de l’écriture de données dans un fichier où nous verrons que la gestion des exceptions est importante.

Manipulez les fichiers

Jusqu’à maintenant, nos programmes sont isolés et n'interagissent pas avec d'autres composants (fichiers, programme, etc.). Dans ce chapitre, nous verrons comment donner un autre pouvoir à nos programmes : manipuler des fichiers.

Lisez des fichiers

En Java, il peut parfois être utile de lire le contenu d’un fichier pour récupérer des données. Qu’il s’agisse d’un fichier que nous avons déjà sauvegardé ou d’un autre fichier, il est possible de le lire, ligne par ligne. Voyons ça avec un exemple :

public class Main1 {

    public static void main(String[] args) {
        
        try {
            // Création d'un fileReader pour lire le fichier
            FileReader fileReader = new FileReader("/path/to/the/file");
            
            // Création d'un bufferedReader qui utilise le fileReader
            BufferedReader reader = new BufferedReader(fileReader);
            
            // une fonction à essayer pouvant générer une erreur
            String line = reader.readLine();
            
            while (line != null) {
                // affichage de la ligne
                System.out.println(line);
                // lecture de la prochaine ligne
                line = reader.readLine();
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Ici, nous ne faisons qu’afficher chaque ligne de notre fichier, mais intéressons-nous aux différents objets de notre code :

  • Le try catch qui nous permet de gérer les exceptions.

  • Un FileReader qui va lire le fichier au chemin qui lui est donné en argument.

  • Un BufferedReader qui va utiliser le fileReader pour lire le fichier ligne par ligne.

  • Une condition while qui nous permet de répéter le code tant que nous ne sommes pas à la fin du fichier.

Écrivez dans des fichiers

Nous venons de voir comment lire un fichier, maintenant voyons comment écrire dans un fichier. Il peut être intéressant pour nos programmes Java de pouvoir écrire dans un fichier pour plusieurs raisons, comme par exemple pour traiter des fichiers de données (XML, CSV, etc.). Il est courant d’utiliser un programme informatique pour traiter ou modifier de gros volumes de données, et Java peut tout à fait s’en occuper  💪.

Notre programme pourrait aussi créer et écrire dans un fichier uniquement destiné à être lu plus tard, ce qui revient à sauvegarder l’état du programme. Prenons un exemple pour enregistrer les paramètres d’un programme Java :

public class Main2 {

    public static void main(String[] args) {

        try {
            // Création d'un fileWriter pour écrire dans un fichier
            FileWriter fileWriter = new FileWriter("/path/to/the/file", false);

            // Création d'un bufferedWriter qui utilise le fileWriter
            BufferedWriter writer = new BufferedWriter(fileWriter);

            // ajout d'un texte à notre fichier
            writer.write("preferenceNewsletter=false");

            // Retour à la ligne
            writer.newLine();

            writer.write("preferenceColor=#425384");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Dans notre exemple, on enregistre les textes  "preferenceNewsletter=false"  et  "preferenceColor=#425384"  dans notre fichier. On utilise la méthode  newLine  du  BufferedWriter  pour changer de ligne entre les 2 textes.

Pour écrire notre texte, nous avons donc dû utiliser plusieurs objets qui fonctionnent ensemble :

  • le “try catch” qui nous permet de gérer les exceptions ;

  • un “FileWriter” qui va préparer le fichier au chemin qui lui est donné en argument ;

  • un “BufferedWriter” qui va utiliser le “fileWrite” pour écrire dans le fichier.

En résumé

  • La lecture du contenu des fichiers se fait grâce à un ”BufferedReader“ qui va lire le fichier ligne par ligne.

  • L’écriture de texte dans un fichier se fait avec un “BufferedWriter“.

Lorsqu’on manipule des fichiers, il faut toujours gérer les exceptions car les fichiers sont des éléments extérieurs à notre programme, et les problèmes d’accès ou de droits sont fréquents.

Écrivez votre premier code Lambda

Nous avons vu dans les parties précédentes de ce cours comment instancier des variables et obtenir ainsi ce qu’on appelle une référence. Il peut s'agir :

  • d'une référence vers un type primitif, par exemple  int cpt = 0  ; 

  • ou d'une référence vers une instance d’objet, par exemple   FigureGeo myFigureGeo  = new FigureGeo()  .

Dans ce chapitre, nous allons voir qu'une référence peut également porter sur un bloc de code !

Écrivez une fonction Lambda

Depuis Java 8, il est désormais possible d’obtenir une référence vers une méthode. C’est ce qu’on appelle une “closure” en anglais, et en Java, c’est la fonctionnalité Lambda qui en est l’expression. Les Lambda sont une nouvelle manière d’écrire du code compact (et plus lisible).

Au-delà d'un code plus compact, un des des grands intérêts des expressions Lambda est d’avoir une alternative aux appels de méthodes à partir de classes anonymes. Sans entrer dans les détails, avant les Lambda, on utilisait souvent les classes anonymes dans le seul but de redéfinir et d’appeler la méthode d’une classe à un endroit bien précis de notre code.

Prenons un exemple :

public class MaClassPrincipale {

   public static void main(String... args) {

      Bird bird = new Bird() {
         @Override //sert simplement à indiquer que nous redéfinissons la méthode fly()
         void fly() {
            System.out.println("Vole grâce à ses deux ailes...");
         }
      }; // Attention à ne pas oublier le “;”

      // À ce moment (et uniquement à ce moment), nous redéfinissons la manière de voler d’un oiseau. Ensuite nous l'utilisons.
      bird.fly(); // Va afficher "Vole grâce à ses deux ailes..."
      // À noter qu’il est toujours possible de passer des paramètres à une méthode d’une classe anonyme
   }

}

Bien entendu, la classe   Bird  pourrait avoir bien d’autres méthodes qu’on serait tenté d’utiliser de la même manière. Mais croyez-moi, ça devient vite ingérable, et on fait l’impasse sur la force de la POO. 😉

Pour arriver aux "closures" et utiliser les Lambda, Java 8 a introduit ce qu’on appelle les interfaces fonctionnelles. Celles-ci n’ont (ou n’auront) qu’une seule méthode abstraite. À l’exécution du code Lambda, Java fait en sorte de déterminer la méthode à appeler.

Ayez à l’esprit qu’une expression Lambda est en quelque sorte la redéfinition d'une méthode d'une interface fonctionnelle, sans avoir à faire une classe anonyme.

Allez, voyons à quoi ressemble un code Lambda (une redéfinition de l’unique méthode d’une interface fonctionnelle) :

() -> action  ou

(parametre, ...) -> une action  ou

(parametre, ...) -> {traitement, retourner quelque chose}  .

Voici un premier exemple :

//Votre interface “fonctionnelle”
public interface Etudiant {
   void donnerNom();
}

//Utilisation
public static void main(String[] args) {
   Etudiant et = () -> {System.out.println("Toto");};
   et.donnerNom();//Affichera Toto
}

Un autre exemple : nous faisons évoluer l’unique méthode de l’interface fonctionnelle.

public interface EtudiantEvolue {
   void donnerNom(String nom);
}

//Utilisation
public static void main(String[] args) {
   EtudiantEvolue et = (leNomAafficher) -> {System.out.println(“Je me nomme+ leNomAafficher);};
   et.donnerNom("Jean");//Cette fois, on affichera Jean
}

Une expression Lambda, c'est toujours :     <liste de paramètres> -> <le corps de la fonction>   . Détaillons chaque élément :

  • <liste de paramètres>  : indique les paramètres en entrée de l'unique méthode de l'interface fonctionnelle. Ils seront saisis entre   ()  et il n'est pas obligatoire d'indiquer leurs types ;

  • ->  : c'est le marqueur de l'expression Lambda. Il sépare la liste des paramètres du corps de la fonction ;

  • <le corps de la fonction>  : entre   {}  ou pas, s'il n'y a qu'une expression courte.

Le corps d’une expression Lambda

Le corps d'une expression Lambda est défini à droite de l'opérateur   ->  . Il peut être :

  • une expression unique ;

  • un bloc de code composé d'une ou plusieurs instructions entourées par des accolades.

Le corps de l'expression doit respecter certaines règles :

  • il peut n'avoir aucune, une seule ou plusieurs instructions ;

  • lorsqu'il ne contient qu'une seule instruction, les accolades ne sont pas obligatoires, et la valeur de retour est celle de l'instruction si elle en possède une ;

  • lorsqu'il y a plusieurs instructions, elles doivent obligatoirement être entourées d'accolades ;

  • la valeur de retour est celle de la dernière expression, ou   void  si rien n'est retourné.

Si le corps est simplement une expression, celle-ci est évaluée, et le résultat de cette évaluation est renvoyé s'il y en a un.

En résumé 

  • Une expression Lambda est une référence vers un bloc de code.

  • Une interface fonctionnelle est une interface qui n’a qu’une seule méthode abstraite.

  • On peut utiliser une expression Lambda pour implémenter une interface fonctionnelle sans déclarer de classe abstraite.

Téléchargez la fiche résumé du cours

C'est bientôt la fin de ce cours, mais avant, je vous propose un dernier quiz pour tester vos connaissances. C'est parti ! 

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