Fil d'Ariane
Mis à jour le mardi 7 mars 2017
  • Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Créez vos propres classes

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

On a manipulé quelques classes simples dans le premier chapitre : Int, Double, Char, String et Boolean. On a vu que chaque classe possède des méthodes que toutes ses instances peuvent utiliser (par exemple + pour les Int et Double, charAt pour les String, etc.). Dans ce chapitre, on va créer nos propres classes et leur ajouter des méthodes. Je vous préviens tout de suite, ce chapitre est l'un des plus importants dans la programmation en Scala, puisque tout ce qu'on va faire tout au long de ce tutoriel est de créer et utiliser des classes ! :o Je ne dis pas ça pour vous effrayer, rien n'est difficile et tout sera expliqué à partir de zéro, mais ne lisez pas ce chapitre si vous n'êtes pas en pleine forme. :p

Pourquoi doit-on créer des classes ?

Rappelons d'abord quelques notions que l'on a vues dans le second chapitre.
On a vu que « bonjour » et « zéro » sont dits des objets de classe String ou des instances de String.
On a deux types de variables, les vars (variables mutables) et les vals (variables constantes) :

scala> var x: Int = 0
x: Int = 0

scala> x = 5
x: Int = 5

scala> val y: Char = 'X'
y: Char = X

On a rencontré quelques méthodes :

scala> val x: java.lang.String = "Une Chaine !"
x: java.lang.String = "Une Chaine !"

scala> x charAt 0
res0: Char = U

scala> x.charAt(0)
res1: Char = U

Et on a découvert la structure conditionnelle if-else :

var a: Int = if (5 > 6) 8 else 9
a: Int = 9

Vous devez connaître toutes ces notions maintenant.

On va désormais créer nos propres classes, nos méthodes et nos objets.

On peut faire ça ? C'est difficile ? Et pourquoi doit-on le faire ?

Vous posez trop de questions :pirate: (mais c'est toujours comme ça au début :lol: ) ! Oui, on peut le faire, et il n'y a rien de difficile, il suffit de suivre attentivement. La troisième question est celle à laquelle il est le plus difficile de répondre, on va donc commencer par prendre un cas de figure.
Supposons qu'on va cloner le célèbre jeu de plateforme Super Mario en console (vous apprenez la programmation pour pouvoir faire des jeux, non ? ;) ). Que faire ?... C'est difficile de savoir puisque vous n'avez pas encore trop de connaissances en programmation, mais supposons qu'il y a une classe Joueur qui ne représente pas des nombres ou des chaines, mais des Joueurs d'un jeu vidéo. Supposons aussi qu'elle dispose de plusieurs méthodes comme « sauter », « attaquer »...
Enfin, supposons qu'on a une variable nommée mario de classe Joueur.
Observez maintenant ce programme :

scala> mario sauter
res0: java.lang.String = "Je saute !!"

scala> mario sauter
res1: java.lang.String = "Je saute !!"

scala> mario battre ennemi
res2: java.lang.String = "J'ai battu l'ennemi !!"

scala> mario entrerDansLeChateau
res3: java.lang.String = "Je suis dans le chateau !! J'ai peur !!!"

scala> mario battre boss
res4: java.lang.String = "J'ai battu le boss !!"

scala> mario donnerBisou princesse
res5: java.lang.String = "Mwaaaaaaaaaaaaaah !!"

C'est super, notre jeu ! :D
Dans ce jeu, on utilise la variable mario pour appeler quelques méthodes qui affichent à l'écran ce qu'il fait : il saute, resaute, tue un ennemi, entre dans le château, bat le boss et sauve la princesse Peach. On a oublié d'afficher « The End », mais ce n'est pas grave.

J'ai bien dit « supposons qu'il existe », mais malheureusement cette classe n'existe pas. :(
Donc, s'il y avait une classe Joueur (et un objet mario de classe Joueur), on aurait pu créer un petit jeu de Mario. Imaginez qu'il y ait aussi des classes qui représentent votre maison, vos vêtements, votre ordinateur, vos parents, etc. On aurait pu mettre toute votre vie quotidienne dans l'ordinateur. o_O

Je ne suis pas en train de faire un mauvais rêve. Tout ce que je viens de vous dire peut être facilement réalisé avec la programmation ! :waw: Il suffit de savoir créer toutes ces classes (puisqu'elles n'existent pas encore), et c'est ce qu'on va apprendre dans quelques minutes.
Maintenant, je peux répondre à votre question : on doit créer d'autres classes car on veut faire des choses plus compliquées qu'écrire du texte ou calculer 1 + 3.
Même si vous n'avez pas trop saisi l'intérêt de tout ça, continuez la lecture. Vous allez mieux comprendre avec les exemples, les exercices et les travaux pratiques (TP).

Notre première classe : Zéro

Puisque nous sommes tous des Zéros, on va commencer par créer la classe « Zero » qui va représenter... les Zéros :p (comment ça, vous n'êtes pas des Zéros ? Sortez vite d'ici ou j'appelle la police les modérateurs :pirate: ).
Vous vous souvenez du mot-clé var ? On a dit qu'il annonce à Scala qu'on va déclarer (créer) une variable. De même, le mot-clé class annonce qu'on va créer une classe.

Voici la syntaxe élémentaire pour créer une classe :

class <NomDeLaClasse>

Il suffit d'écrire le mot-clé class suivi d'un nom. C'est tout. Les règles de nommage pour les variables s'appliquent aussi pour les classes sauf qu'on préfère que la première lettre du nom d'une classe soit en majuscule.
Vous avez hâte de tester ? Moi aussi. :p Allons-y, tapez class Zero et appuyez sur <Entrée>:

scala> class Zero
defined class Zero

« defined class Zero » veut dire « classe Zero définie » donc tout est bien passé. Notre classe est prête à être utilisée.

Comment faire pour l'utiliser ?

Si vous avez bien suivi jusqu'à maintenant, vous devez savoir qu'on ne manipule pas des classes, mais des objets. Il faut donc créer un objet de classe « Zero » (ou encore une instance de la classe Zero) : on dit qu'on instancie la classe. C'est (encore une fois) très simple : on va utiliser le mot-clé new qui signifie nouveau. Voici la syntaxe générale :

new <NomDUneClasse>

où NomDUneClasse désigne le nom d'une classe.
Essayons cela :

scala> new Zero
res0: Zero = Zero@125c99f

On a donc notre variable res0, suivie de « : » et du nom de la classe, comme d'habitude. Après le signe égale, il y a le nom de la classe suivi du symbole '@' et par une succession de chiffres et de lettres (qui change à chaque fois, donc ne hurlez pas « je n'ai pas le même affichage ! », c'est tout à fait normal).

C'est quoi, ça ? On n'a pas eu des affichages pareils avant !

C'est parce que Scala peut afficher un Int, un String ou un Boolean, mais il ne peut pas afficher notre zéro, donc il décide d'afficher sa référence. C'est un peu compliqué d'expliquer ce qu'est une référence pour un débutant sans écrire 22 chapitres sur la mémoire et tout ça, donc je vais beaucoup simplifier les choses. :-°
Votre ordinateur dispose d'un nombre gigantesque de boîtes dans lesquelles il enregistre les informations. Chacune de ces boîtes possède un nom unique (on n'aura jamais deux boîtes ayant le même nom). Lorsqu'on crée un objet, l'ordinateur se trouve obligé de l'enregistrer quelque part pour qu'on puisse l'utiliser plus tard. Il va donc chercher une boîte vide et il va y mettre notre objet. Le nom de la boîte choisie est enregistré dans l'objet, il est appelé référence de l'objet. Lorsqu'on réutilise cet objet (affichage, affectation) l'ordinateur lit la référence et va chercher la boîte qui a ce nom pour pouvoir récupérer les données.

Pas d'autres questions ?

Si ! Pourquoi on n'a pas utilisé new pour créer des instances de Int ?

Bonne question ! Ça me prouve qu'il y en a quelques-uns parmi vous qui suivent bien le cours. En fait, les classes vues dans le chapitre précédent sont un peu spéciales, des classes O.V.N.I V.I.P. o_O
Imaginez qu'on doive créer les entiers avec new, et supposons que pour créer l'entier 1 il faille faire new Int(1), une opération simple comme 1 + 2 / 5 aurait été écrite comme :

scala> new Int(1) + new Int(2) / new Int(5)

:waw:
Du coup, c'est devenu moche, long et illisible. Et n'oubliez pas que dans n'importe quel programme, on utilisera massivement les entiers, les booléens et compagnie. Pour ces raisons, les classes de base s'utilisent d'une manière plus facile que les autres.

Retournons à notre classe. On ne peut jusqu'à présent que créer des objets de type « Zero », les enregistrer dans des variables et afficher leurs références, rien de plus.

scala> val x: Zero = new Zero
x: Zero = Zero@2a5f99b
scala> x
res1: Zero = Zero@2a5f99b

J'avoue que c'est très limité. Un Zéro devrait avoir un nom, un âge et plein d'autres choses. On doit donc ajouter des variables à notre classe.

Variables de classe

Comme le nom l'indique, ce sont des variables déclarées dans le corps d'une classe. Ah oui, j'ai oublié de vous dire qu'une classe peut avoir un corps (désolé :-° ). Il est mis entre deux accolades ('{' et '}') après le nom de la classe. Essayez ce code :

scala> class Zero
defined class Zero

C'est exactement équivalent au précédent code parce qu'on n'a rien mis entre les accolades, on dit que le corps de la classe est vide. Ce mot (vide) sous-entend qu'on peut y mettre quelque chose, et c'est vrai : le corps peut contenir des variables et des méthodes. On va commencer par les variables, on abordera les méthodes dans le chapitre suivant.
Créons une variable mutable « pseudo » de classe String initialisée à « Anonyme » entre les accolades (c'est-à-dire dans le corps) de la classe Zero :

scala> class Zero { var pseudo: java.lang.String = "Anonyme" }
defined class Zero

Essayez maintenant d'utiliser « pseudo » :

scala> pseudo
<console>:6: error: not found : value pseudo
       pseudo
       ^

On a une belle erreur : « non trouvée : valeur pseudo ». Scala ne trouve pas la variable.

Pourquoi ? Pourtant on l'a créée correctement avec var. :'(

Ne pleurez pas, la variable « pseudo » n'est pas perdue, mais lorsqu'on l'a mise dans le corps de la classe elle est devenu une propriété de celle-ci. Pour utiliser « pseudo », il faut d'abord créer une instance « z » de Zero et puis écrire « z.pseudo » (ou « z pseudo ») :

scala> val z: Zero = new Zero
z: Zero = Zero@299fd56

scala> z.pseudo
res0: java.lang.String = "Anonyme"

scala> z.pseudo = "Zozor"

scala> z.pseudo 
res1: java.lang.String = "Zozor"

On a créé ici une variable « z » de classe « Zero », et on a pu accéder enfin à notre variable « pseudo » via « z.pseudo ». On a même pu lui affecter une nouvelle valeur !

Maintenant essayez de créer deux instances de « Zero » nommées « x » et « y » et d'affecter deux valeurs différentes à « pseudo » via « x.pseudo » et « y.pseudo ». Affichez ensuite « x.pseudo » et « y.pseudo ».
Je mets le code en secret, essayez de le faire par vous-mêmes avant de voir la correction.

scala> val x: Zero = new Zero
x: Zero = Zero@299fd56

scala> val y: Zero = new Zero
x: Zero = Zero@56cg522

scala> x.pseudo = "Til0u"

scala> y.pseudo = "iPoulet"

scala> x.pseudo
res2: java.lang.String = "Til0u"

scala> y.pseudo
res3: java.lang.String = "iPoulet"

Si ! Dès qu'on change l'objet que contient la variable on ne peut plus récupérer l'ancien, sauf qu'ici la variable « pseudo » n'existe pas. :o o_O
Rassurez-vous, je vais tout expliquer dans la prochaine sous-partie.

La programmation orientée objet

On va représenter la classe par un grand rectangle dans lequel on peut mettre des variables. Voici le schéma de notre classe Zero :

Image utilisateur

(Je sais, je sais ! Mon hamster et moi, nous avons le même niveau en graphisme. :lol: )
La classe sert de modèle pour la création d'objets. Je m'explique : dès qu'on crée une variable de type « Zero » avec new, Scala se dirige vers la définition (déclaration) de la dite classe, cherche les variables (et les méthodes) qui se trouvent dans le corps de la classe et associe une copie de chacun d'entre eux à la variable.
Dans l'exemple précédent, lorsqu'on a créé la variable « x » de type Zéro, Scala est allé fouiller dans le corps de la classe. Lorsqu'il a rencontré la variable « pseudo », il l'a clonée et a associé le clone à « x » en la nommant « x.pseudo » (et il utilise la même approche avec « y »). Donc « x.pseudo » et « y.pseudo » sont en quelque sorte deux clones différents de « pseudo ».

Image utilisateur

Afin de vous convaincre qu'il ne s'agit pas de la même variable, on va faire le petit test suivant.
Commençons par créer une classe vide A :

scala> class A
defined class A

Ensuite, créons une autre classe B qui contient une variable de type A :

class B { var a: A = new A }
defined class A

Maintenant nous allons créer deux instances b1 et b2 de B et afficher b1.a et b2.a :

scala> val b1 = new B
b1: B = B@1d07e4

scala> val b2 = new B
b1: B = B@58e891

scala> b1.a
res0: A = A@1def49

scala> b2.a
res1: A = A@188d9de

Vous n'avez rien remarqué ? :euh:

b1.a et b2.a n'ont pas la même référence, donc il ne s'agit pas de la même variable. Puisqu'elles sont enregistrées dans deux endroits différents de votre ordinateur, on peut changer l'une (par affectation) sans toucher à l'autre. C'est exactement ce qui s'est passé pour « pseudo » de la classe « Zero ». :)

On peut mettre n'importe quel nombre de variables membres dans une classe. Ajoutez donc une variable « age » de classe « Int » et une autre « signature » de classe String. Ensuite, créez une instance de « Zero » appelée « moi » et affectez votre pseudo, votre âge et votre citation aux variables correspondantes.

scala> class Zero {
     |   var pseudo: java.lang.String = "Anonyme"
     |   var age: Int = 0
     |   var citation: java.lang.String = "Aucune citation"
     | }
defined class Zero

scala> val moi: Zero = new Zero
moi: Zero = Zero@125gf5

scala> moi.pseudo = "Einstein++"

scala> moi.age = 21

scala> moi.citation = "J'adore Scala <3"

Simple comme bonjour. Je n'ai pas fait d'affichage, mais vous êtes grands maintenant, faites-le vous-mêmes.

Pourquoi tu as mis des espaces avant les variables ?

On peut ajouter des espaces n'importe où, ils sont toujours ignorés, mais ils augmentent la lisibilité du code. On appelle ça l'indentation.

Une dernière chose avant la fin de cette sous-partie : pour utiliser la variable « pseudo » dans le corps de la classe (par exemple pour créer une variable qui contient « Pseudo: » + pseudo), on se sert d'un objet spécial nommé « this ».

scala> class Zero {
     |   var pseudo: java.lang.String = "Anonyme"
     |   var uneVariable: java.lang.String = "Pseudo: " + this.pseudo
     |  }

Lorsqu'on crée une variable « z » de classe « Zero », this est remplacé par « z », donc this.pseudo devient « z.pseudo ». ;)

POO

La programmation avec les classes et les objets est appelée programmation orientée objet (POO pour les intimes ;) ). Tout au long de la partie I du tutoriel, on va apprendre ce type (on dit aussi paradigme) de programmation.
Il existe d'autres types, les deux plus célèbres sont :

  • le paradigme procédural (C, Pascal, etc.) : celui-là n'existe pas en Scala ;

  • le paradigme fonctionnel (Lisp et ses dérivés, OCaml, Haskell) : celui-là sera le sujet de la deuxième partie de ce tutoriel. :D

Blocs et inférence

Nous allons nous concentrer dans cette sous-partie sur quelques points syntaxiques du langage.

Les blocs

Un bloc est un ensemble d'expressions mises entre accolades. Voici un exemple :

scala> {
     |  3 + 6
     |  var varBloc: java.lang.String = "une var déclarée dans le bloc"
     |  'A'
     | }
res0: Char = A

Seule la dernière expression du bloc est affichée à l'écran. Pourquoi ? Parce qu'un bloc est une expression (tout comme 1 + 1, if-else...) et comme vous le savez déjà, toute expression renvoie quelque chose (on dit aussi que l'expression est évaluée à ce quelque chose), le bloc est évalué à sa dernière expression, donc à 'A' dans ce cas-là.
Voici un autre exemple :

scala> {
     |  val i: Int = 2
     |  val j: Int = 4
     |  i + j
     | }
res1: Int = 6

Si vous essayez d'utiliser i ou j après le bloc, vous aurez une erreur : not found : value <nom_de_la_variable>
Les variables déclarées dans un bloc n'existent qu'à l'intérieur de celui-ci, on appelle ça la portée des variables.
Prenons cet exemple :

scala> {
     |  val i: Int = 2
     |  val j: Int = {
     |      val k: Int = 4
     |      k * k
     |   }
     |   i + k 
     | }

Quelle est la valeur de j ? Que va retourner le bloc externe ?

La valeur de j est celle retournée par le bloc interne. Ce dernier, comme on l'a dit déjà, est évalué à sa dernière expression qui est k * k. Donc au final j vaut 16 (k == 4 et 4 * 4 == 16).
Le bloc externe ne va pas pouvoir retourner quelque chose car il y aura une erreur. Scala ne va pas trouver la variable k : puisque k est déclarée dans le bloc interne, elle est automatiquement détruite à la fin de celui-ci, donc elle n'existe plus.

À quoi servent les blocs ?

À plusieurs choses. :p Par exemple, si on veut regrouper plusieurs expressions ensemble comme on a fait dans le dernier code.

Bon je vous l'accorde, ce n'est pas très utile, mais il a d'autres utilisations plus importantes comme dans le if-else.
On a vu dans le chapitre précédent qu'une expression conditionnelle « if-else » doit avoir la forme :

if (CONDITION) EXPRESSION1 else EXPRESSION2

EXPRESSION1 et EXPRESSION2 doivent être chacune une et une seule expression. Heureusement, un bloc est considéré comme une seule expression, on peut donc mettre tout un bloc à la place de EXPRESSION1 et EXPRESSION2 :

scala> if (2 > 0) {
     |   val x: Int = 5
     |   x * x + 6
     | } else {
     |   2 + 5
     |   32
     | }
res5: Int = 31

Cool ! On peut désormais mettre autant d'instructions qu'on veut dans un if-else. Et encore, on peut mettre n'importe quel type d'expressions dans un bloc, même un if-else ou un autre bloc !

Bien. Passons à autre chose : l'inférence.

L'inférence

Scala peut inférer quelque chose dans vos programmes, c'est-à-dire que vous pouvez omettre des choses et il les ajoutera lui-même. On va voir dans cette sous-partie uniquement deux types d'inférence, les autres seront introduits au bon moment.

L'inférence des « ; »

Les plus curieux d'entre vous ont sûrement essayé d'écrire plusieurs expressions sur une même ligne. Mais, hélas, il se sont sûrement retrouvés avec une erreur (les pauvres :p ). Même si vous n'êtes pas si curieux que ça, essayez d'écrire scala> val x: Int = 0 val y: Double = 2.3. Le gentil interpréteur nous sort une petite insulte erreur bizarre : « ; expected but 'val' found » o_O (traduction pour les anglophobes : « ; attendu mais 'val' trouvé »).

Mais de quel « ; » parle-t-il ? C'est une blague ?

Si vous avez déjà programmé en C (Java, Pascal, etc.) alors vous devez savoir que chaque instruction finit par un « ; ». Pour ceux qui ne l'ont jamais fait, voici un code C (extrait du tutoriel officiel de ce site) :

int nombre = 2;
nombre += 4; 
nombre -= 3; 
nombre *= 5; 
nombre /= 3; 
nombre %= 3;

Remarquez que chaque instruction doit finir par un « ; ». C'est pareil ici, sauf que Scala les met pour nous : c'est l'inférence des points-virgules. :D Un « ; » est automatiquement ajouté à la fin de chaque ligne, sauf si cette dernière finit par un « , » ou un opérateur (+, *, ::, etc.), ou s'il y a une parenthèse non fermée.
Dans l'exemple précédent, un seul « ; » a été ajouté à la fin de la ligne donc on doit ajouter un deuxième entre les deux instructions explicitement :

scala> val x: Int = 0 ; val y: Double = 2.3

Vous pouvez écrire tous les « ; » dans votre programme si vous voulez.

L'inférence des types des variables

Un autre type d'inférence, plus important que le premier, est l'inférence des types des variables (var et val) :

scala> val x = "Bonjour"
x: java.lang.String = "Bonjour"

scala> val z = new Zero
z: Zero = Zero@52ff5d

Du coup, on peut écrire moins de code pour avoir le même résultat. Notez que lorsqu'on ne met pas les types, Scala les ajoute avant de transformer le code en Bytecode Java. La seule différence entre les deux notations est le nombre de lettres écrites. :D

Et comment fait-il pour deviner le type tout seul ?

C'est simple : il regarde la valeur initiale de la variable (« Bonjour » et new Zero), détermine sa classe et associe cette classe à la variable.

C'est tout pour ce chapitre, vous pouvez aller faire les exercices. :-°

Exercices

Tout comme le chapitre précédent, une série d'exercices vous attend. Bon travail. ;)

Exercice 1

Énoncé
  • Quel mot-clé doit-on utiliser pour déclarer une classe ?

  • Quel mot-clé doit-on utiliser pour instancier une classe ?

Indications

déclarer == créer
instancier == créer une instance de

Solution

On utilise class pour la déclaration et new pour l'instanciation.

Exercice 2

Énoncé

J'ai voulu afficher un objet et la console m'a sorti PrisonBreak@256lk2d. Quelle est la classe de mon objet ?

Indications

Fouillez bien le cours.

Solution

L'objet est de type PrisonBreak (tout ce qui est avant le '@').

Exercice 3

Énoncé
  • Créez les classes vides Lit, Chaise et Bureau.

  • Créez une classe Chambre qui contient des instances des classes ci-dessus.

  • Créez une instance de Chambre. Qu'affiche la console ? Pourquoi ?

Indications

Créez un corps à votre classe Chambre.

Solution
scala> class Lit
defined class Lit

scala> class Chaise
defined class Chaise

scala> class Bureau
defined class Bureau

scala> class Chambre {
     |   var lit = new Lit
     |   val chaise = new Chaise
     |   val bureau = new Bureau
     |  }
defined class Chambre

scala> var maChambre: Chambre = new Chambre
maChambre: Chambre = Chambre@124lk21

La console affiche le nom de la classe suivi de '@' puis de la référence de l'objet maChambre parce qu'elle ne sait pas comment afficher les instances des classes qu'on crée.

Exercice 4

Énoncé

Dans l'exemple précédent, j'ai omis les noms des classes dans la déclaration des quatres variables parce que Scala peut les deviner tout seul. Comment appelle-t-on cette fonctionnalité ?

Indications

Je ne sais rien, moi. :-°

Solution

Oui, je me souviens maintenant ! C'est l'inférence des types.

Exercice 5

Énoncé

Lequel de ces caractères Scala insère-t-il à la fin de chaque ligne ?

  • un « : »

  • un « ; »

  • une « , »

Comment appelle-t-on cette fonctionnalité ?

Indications

La troisième proposition est fausse.

Solution

Scala ajoute un point-virgule à la fin de chaque ligne. Il s'agit de l'inférence des point-virgules.

Citation : Planète POO

Alerte ! Alerte ! Des intrus ! Les Zéros nous envahissent ! Tous les soldats sont invités à rejoindre le champ de bataille !

J'espère qu'il n'y avait pas beaucoup de morts de notre côté durant ce premier combat contre le monde de la POO. :euh: Ce n'était pas si difficile que ça, non ? Reprenez ce niveau chapitre dès le début si vous avez encore des doutes parce que, comme je l'ai dit au début, les classes sont la base de tout.

Ce n'est pas la fin de la bataille en tout cas, la POO sortira prochainement toutes ses armes secrètes : méthodes, héritage, polymorphisme... >_
Ne vous inquiétez pas, ensemble nous sommes imbattables. :soleil:

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