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 !

Méthodes (1/2)

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

Je vous ai dit la dernière fois qu'une classe peut contenir des variables et des méthodes. Ces dernières vont rendre nos classes plus interactives. Elles vont aussi nous libérer de la répétition d'un même code plusieurs fois.

En somme c'est un concept assez fondamental et présent dans n'importe quel langage de programmation (elles sont appelées fonctions dans plusieurs langages ;) cependant cette dénomination n'est pas valide en Scala).

Prêts ?

1, 2, 3, partez ! :p

Méthodes simples

La dernière version qu'on a écrit de la classe « Zero » était celle-ci (en laissant Scala inférer les types) :

scala> class Zero {
     |   var pseudo = "Anonyme"
     |   var age = 0
     |   var citation = "Aucune citation"
     | }

Notre zéro a un pseudo, un âge et une citation, parfait ! Le seul bémol est qu'on ne peut pas l'afficher car Scala affichera seulement son adresse. Que faire ?

Ça sonne bien, essayons cela :

scala> class Zero {
     |   var pseudo = "Anonyme"
     |   var age = 0
     |   var citation = "Aucune citation"
     |   var affichage = "Pseudo: " + pseudo +" Age: " + age + " Citation: " + citation
     | }

On crée une variable « affichage » de classe java.lang.String initialisée à la concaténation de plusieurs chaines. Rien de difficile.
Testons maintenant le code :

scala> val z = new Zero
z: Zero = Zero@214se58

scala> z.affichage
res0: java.lang.String = "Pseudo: Anonyme Age: 0 Citation: Aucune citation"

scala> z.age = 15

scala> z.affichage
res1: java.lang.String = "Pseudo: Anonyme Age: 0 Citation: Aucune citation"

Au début tout parait nickel, on a eu l'affichage qu'on cherchait. Mais, après le changement de la variable « age », « affichage » n'a pas été mis à jour. En fait, lorsque « age » est créée, elle vaut 0, donc à l'initialisation de « affichage » on a concaténé 0 avec la chaine. Après, lorsqu'on a changé la valeur de « age », rien n'a changé pour affichage car la valeur d'une variable ne peut changer que par affectation, chose qu'on n'a pas faite. Donc c'est impossible de gérer l'affichage avec une variable, on doit utiliser une méthode.

La syntaxe générale d'une méthode simple est :

def <NomDeLaMethode> : <TypeDeRetour> = EXPRESSION

Avec :

  • def : mot-clé qui annonce qu'on va déclarer une méthode.

  • <NomDeLaMethode> : un nom qui obéit aux mêmes règles de nommage que les variables.

  • <TypeDeRetour> : c'est la classe de EXPRESSION. On dit que la méthode retourne un <TypeDeRetour> (par exemple la méthode retourne un Int, un Boolean).

  • EXPRESSION : n'importe quelle expression.

C'est presque la déclaration d'une variable, seul le mot-clé change. On l'appelle aussi de la même manière qu'une variable, à savoir en utilisant un point suivi du nom de la méthode.

C'est donc une variable ?

Non, la différence entre une variable et une méthode est que EXPRESSION est réévaluée à chaque appel de la méthode.
Changeons affichage en une méthode et observons :

scala> class Zero {
     |   var pseudo = "Anonyme"
     |   var age = 0
     |   var citation = "Aucune citation"
     |   def affichage = "Pseudo: " + pseudo +" Age: " + age + " Citation: " + citation
     | }
defined class Zero

scala> val z = new Zero
z: Zero = Zero@214se58

scala> z.affichage
res0: java.lang.String = "Pseudo: Anonyme Age: 0 Citation: Aucune citation"

scala> z.age = 15

scala> z.affichage
res1: java.lang.String = "Pseudo: Anonyme Age: 15 Citation: Aucune citation"

Super ! Ça marche comme si c'était de la magie ! :magicien: Il n y a rien de magique : à chaque fois qu'on écrit « z.affichage » (donc à chaque fois qu'on appelle « affichage »), Scala réévalue EXPRESSION et affiche sa valeur. Donc à chaque appel, on refait la concaténation ("Pseudo: " + pseudo +" Age: " + age + " Citation: " + citation ) ce qui fait qu'on aura toujours les valeurs actuelles des variables et non pas des anciennes versions.

On a déjà vu des méthodes simples comme celle-ci : length de la classe String et toInt de la classe Char. Il suffit d'écrire objet.nomDeLaMethodeSimple pour invoquer (appeler) de telles méthodes.

Allez-y, ajoutez une autre méthode « avancerAge » qui ajoute 1 à la variable « age ».

def avancerAge = age = age + 1

Exemple d'utilisation :

scala> val z = new Zero
z: Zero = Zero@125fh8

scala> z.age
res2: Int = 0

scala> z.avancerAge

scala> z.age
res3: Int = 1

Quelques remarques :

  • L'expression « age = age + 1 » peut être simplifiée en « age +=1 ». En général, si o1 et o2 sont deux objets, l'expression o1 = o1 methode o2 peut étre écrite o1 methode= o2. Par exemple x *= 6, x /= 2, bool &&= true, etc.).

  • Lors de l'utilisation de la méthode, Scala n'affiche rien parce que la méthode ne retourne rien. En fait, vous ne vous êtes jamais demandé quel est le type de l'expression d'affectation age += 1 ? Elle a un type nommé Unit, ce type est l'un des types V.I.P. et signifie « rien ». On sait déjà que Boolean n'a que deux instances : true et false, mais Unit a une seule instance qui est « () ». Dès que l'interpréteur rencontre l'objet (), il n'affiche rien et continue son chemin.
    Comme tout objet, on peut mettre () dans une variable :

    scala> val u = ()
    u: Unit = ()

    Donc si on veut déclarer explicitement le type de retour de avancerAge, la méthode devrait avoir cette forme :

    def avancerAge : Unit = age += 1
Utilisation d'un fichier

Avec l'ajout des méthodes, la classe devient un peu volumineuse et il devient fastidieux de devoir tout réécrire dans la console à chaque fois. On va donc écrire le code dans un fichier. Ouvrez un éditeur de texte simple (Bloc-notes par exemple) et mettez-y le code de la classe Zero :

class Zero {
  var pseudo = "Anonyme"
  var age = 0
  var citation = "Aucune citation"
  def affichage = "Pseudo: " + pseudo + " Age: " + age + " Citation:" + citation
  def avancerAge = age += 1
}

Enregistrez votre fichier sous le nom « Zero.scala » puis tapez dans l'interpréteur :

scala> :load <CheminDuFichier>\Zero.scala

Vous devez préciser l'emplacement du fichier (<CheminDuFichier>), voici un exemple :

scala> :load C:\Zero.scala
"Loading C:\Zero.scala"

class Zero defined

Scala vous prévient qu'il est en train de charger le fichier (Loading C:\Zero.scala) et ensuite, il annonce qu'une classe Zero a été créée.

Vous pouvez mettre n'importe quel nombre et n'importe quel type d'expression dans le fichier, elles seront interprétées dans l'ordre. Ajoutez ces expressions après la déclaration de la classe :

val z = new Zero
z.pseudo = "Un Zéro"
z.age = 12
z.citation = "Ce tuto est génial !!"
z.affichage

Enregistrez votre code et chargez votre fichier, vous devez voir ceci sur l'écran :

scala> :load C:\Zero.Scala
"Loading C:\Zero.scala"

class Zero defined

z: Zero = Zero@32az48

res4: java.lang.String = "Pseudo: Un Zéro Age: 12 Citation: Ce tuto est génial"

On pouvait faire ça et tu nous as obligés à taper toutes ces lignes dans la console moche toute noire ? :'( Pourquoi ?

Parce que je suis méchant. :diable: Plus sérieusement, vous aurez un jour ou l'autre besoin de travailler avec la console, surtout si vous voulez faire du web avec Scala (Lift, Play!) donc il vaut mieux se familiariser avec elle dès le début (cependant il n' y aura aucune notion de programmation web dans ce tutoriel).

Les commentaires

Lorsqu'on écrit du code qu'on veut passer à quelqu'un d'autre (par exemple pour le poster sur le forum) il sera judicieux de le commenter. Les commentaires sont des textes ignorés par Scala, donc vous pouvez écrire n'importe quoi (explication de la signification d'une expression, d'une classe, d'une méthode...).
Il existe trois types de commentaires :

  • les commentaires qui tiennent sur une seule ligne : ils commencent par « // » et continuent sur toute la ligne ;

  • les commentaires qui tiennent sur plusieurs lignes : ils sont délimités par /* et */, ils peuvent être écrits sur plusieurs lignes ou une partie de la ligne ;

  • les commentaires de génération de la Scaladoc : ils sont un peu plus compliqués que les autres, une annexe leur sera donc consacrée.

Voici un exemple d'utilisation des deux premiers types de commentaires :

//Cette classe représentera les zéros
class Zero {
  var pseudo = "Anonyme"
  var age /*Attention, pas d'accent*/ = 0
}
/* Il y aura par la suite d'autre classes comme
Zozor, Mario et Pokemon ! */

Évitez les commentaires inutiles du genre :

/* C'est la version 1.1 de ce programme
   Tous droits réservés !!!
   Vous devez me donner 2000 euros avant d'utiliser 
   mon super programme qui écrit bonjour en console
   LoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOL*/

et les commentaires évidents :

i += 1 // on ajoute 1 à i

Merci. :)

Méthodes à arguments

On a vu dans le chapitre 2 la méthode « charAt », qui retourne le caractère de la chaine qui se trouve à une position donnée.

scala> "Bonjour" charAt 0
res0: Char = B

scala> "Bonjour".charAt(0)
res1: Char = B

La deuxième notation ressemble un peu à celle qu'on a utilisée pour les méthodes simples (utilisation du point) sauf qu'il y a des parenthèses qui contiennent un Int.

C'est quoi cet Int ?

C'est un argument de la méthode. Souvent, les méthodes ont besoin d'informations supplémentaires pour assurer un fonctionnement correct. Par exemple, « charAt » a besoin de la position du caractère qu'elle va retourner et « + » a besoin d'un deuxième nombre pour pouvoir faire l'addition.
La structure générale d'une méthode à un argument est :

def <NomDeLaMethode>(<NomDeLArgument>: <TypeDeLArgument>) : <TypeDeRetour> = EXPRESSION

Vous devez être capables de lire et comprendre ces notations tout seuls, vous n'êtes plus des nouveaux arrivants dans le monde de la programmation. ;)
Comme exemple, on va ajouter à la classe « Zero » une méthode « ajouterAge » qui prend un Int en argument et l'ajoute à « age » :

def ajouterAge(n: Int) = age += n

Elle ressemble beaucoup à « avancerAge », sauf que celle-ci ajoute le nombre « n » passé en argument au lieu de 1.
Faisons nos tests :

val z = new Zero
z.age
z.ajouterAge(5)
z.age
z.ajouterAge(12)
z.age

Rechargez le fichier Zero.scala et vous aurez :

z: Zero = Zero@124587
0
5
17

On crée tout d'abord une variable (constante) « z » de classe « Zero », on affiche la variable « z.age » (elle vaut 0), on lui ajoute 5, on la réaffiche (0 + 5 = 5), on lui rajoute 12 et on l'affiche pour la dernière fois (5 + 12 = 17).

Qu'est-ce qui s'est passé ? Lorsqu'on a fait « z.ajouterAge(5) » la méthode « ajouterAge » a été évaluée en remplaçant l'argument « n » par 5. Donc l'expression « age += n » a été remplacée elle aussi par « age += 5 ». :magicien: De même, lorsqu'on a appelé « z.ajouterAge(12) », « n » a pris la valeur 12 et donc on a réellement fait « age += 12 ». Donc « n » est juste là « pour la forme », il sera toujours remplacé par quelque chose de concret. Pour cette raison on l'appelle parfois « argument formel ».
Pour m'assurer que vous avez bien compris, écrivez une méthode :

  • « multiplierAge » qui prend en argument un Int « n » et affecte à « age » sa valeur multipliée par « n » ;

  • « + » qui prend en argument un Int « n » et retourne la somme de « age » et « n » (sans modifier « age »).

def multiplierAge(n: Int) = age *= n
def +(n: Int) = age + n // ou age.+(n)

Tout comme pour les variables, on peut utiliser directement la méthode dans la classe où elle est définie (« this » sera ajouté implicitement). Voici la méthode avancerAge réécrite en utilisant ajouterAge :

def avancerAge = ajouterAge(1) // def avancerAge = this.ajouterAge(1)
Notations

Toutes les méthodes (à arguments ou non) peuvent être appelées de deux façons différentes :

o.methode
o methode

(Pour les méthodes sans argument, on préfère la première notation.)
Pour les méthodes qui prennent un seul argument (on verra dans quelques instants les méthodes qui prennent plusieurs arguments ;) ) on a trois notations possibles :

o1.methode(o2)
o1 methode(o2)
o1 methode o2

La nouvelle notation est la troisième, elle est la seule à avoir un nom : la notation infixe. Elle est très importante parce que c'est grâce à elle qu'on peut écrire x + y au lieu de x.+(y). On la nomme parfois « la notation opérateur ».

Généralisation

En réalité, une méthode peut avoir n'importe quel nombre d'arguments, et pas seulement un. Les différents arguments sont séparés par des virgules :

def <NomDeLaMethode>(<NomDeLArgument1>: <TypeDeLArgument1>,
                     <NomDeLArgument2>: <TypeDeLArgument2>,
                      ...,
                     <NomDeLArgumentN>: <TypeDeLArgumentN>) : <TypeDeRetour> = EXPRESSION

D'abord, qui peut me rappeler pourquoi j'ai le droit de retourner à la ligne sans avoir de problèmes avec l'inférence des points-virgules ? Personne ? :'( Bon, je ne vais pas vous répondre, allez relire le paragraphe sur l'inférence tout seuls. :colere:

Retournons à nos méthodes à plusieurs arguments. On va créer une classe Calculatrice qui aura seulement une méthode somme2 qui prend deux Int et retourne leur somme et une méthode somme3 qui fait la même chose pour 3 entiers. Essayez de le faire sans voir la correction.

class Calculatrice {
  def somme2(x: Int, y: Int) = x + y
  def somme3(x: Int, y: Int, z: Int) = x + y + z
}
val c = new Calculatrice
c.somme2(5, 6)
val x: Int = 5
c.somme2(x, 2 * x)
c.somme3(1, 8, x)

Recodez maintenant somme3 en utilisant somme2.

def somme3(x: Int, y: Int, z: Int) = somme2(x, somme2(y, z))

Rien de compliqué, comme d'habitude.
Je vais vous poser une autre question. Si on veut créer une méthode qui fait plusieurs instructions, que faire ? Je suis sûr que certains d'entre vous ont trouvé la réponse : les blocs. Encore une fois, les blocs nous montrent leur puissance et leur importance.

def methode = {
  var x = 5
  x += 2
  x *= 8
  x %= 3
  x
}

Si vous appelez une méthode à plusieurs arguments de classes différentes, vous devez lui passer des objets des mêmes classes que les arguments et dans le même ordre. Par exemple, si on considère cette méthode :

def m(a: Double, b: Boolean, c: Chaise, d: Char) = //le corps ne nous intéresse pas

Pour appeler « m » correctement, il faut mettre entre parenthèses :

  • un objet Double (1.5, 3.1415...) suivi par,

  • un Boolean (true ou false) suivi par,

  • une instance de Chaise suivie par,

  • un Char.

Voici quelques exemples d'appels :

//Appels corrects
o.m(5.3, true, new Chaise, '5')
val c = new Chaise ; o.m(20.0, 22.32 < 0, c, '@')
//Appels incorrects
o.m(1.1, new Chaise, false, '8') //erreur : on a mis l'instance de Chaise avant celle de Boolean
o.m(0.23, true, new Chaise) //erreur : il faut passer quatre arguments et non pas trois

Variable ou méthode ?

On a vu les principaux composants du corps de la classe : les variables et les méthodes. On les appelle champs de la classe.

Un des problèmes que vous allez rencontrer dans vos premiers programmes, lorsque vous déciderez de créer des classes, est de choisir entre ajouter une variable ou une méthode. Dans le début de ce chapitre, on a essayé de déclarer affichage comme une variable et lorsque ça n'a pas marché on l'a modifié en une méthode. Vous n'allez pas tâtonner toute votre vie, c'est très simple de différentier ce qui doit être une variable de ce qui doit être une méthode.

Comment faire alors ?

En suivant ces deux règles :

  • les propriétés sont représentées par des variables (nom, points de vie, ordinateur, longueur, poids...) ;

  • les actions sont représentées par des méthodes (sauter, manger, afficher, dormir...).

En plus, on préfère nommer les méthodes par des verbes et les variables par des noms pour pouvoir facilement distinguer une propriété d'une action (renommez donc « affichage » en « afficher » ;) ).

C'est tout pour ce chapitre. L'heure des exos a sonné. ^^

Exercices

Exercice 1

Énoncé

Avec quel mot-clé déclare-t-on une méthode ?
Quelle est la différence entre ces deux lignes de code ?

var x = "5" * i // i est une variable de classe Int
def m = "5" * i
Indications

Relire les explications pour « affichage ».

Solution

On déclare une méthode avec le mot-clé def.
La première ligne est une déclaration de variable. Même si la valeur de i change, x reste constant (si on n'effectue aucune affectation). Par contre, m est réévaluée à chaque appel.

Exercice 2

Énoncé

Quel type d'inférence a-t-on vu dans ce chapitre ?

Indications

On n'a pas parlé d'inférence ici. :-°

Solution

C'est l'inférence du type de retour des méthodes.

Exercice 3

Énoncé
def m(i: Int) = if(i > 0) x = i else x = -i

Comment appelle-t-on i ?
Quel est le type de retour de m ?

Indications

i est un... formel.
m retourne « () ».

Solution

i est un argument (formel) de la méthode.
m est de type (de retour) Unit.

Exercice 4

Énoncé
def m() = "7" * 7

Quelle est l'appel erroné parmi ces possibilités ?

  • o m

  • o m()

  • o.m

  • o.m()

Indications

Le quatrième appel est correct.

Solution

Tous les appels sont corrects.

Exercice 5

Énoncé

On va ajouter la possibilité de recevoir et d'envoyer des messages aux Zéros.

  • Commencez par ajouter la variable « msg » de classe String qui va enregistrer le message.

  • Ajoutez une méthode envoyerMessage qui prend en paramètres un String et un Zéro et qui envoie le message au Zéro.

  • Ajoutez une méthode lireMessage qui affiche le message à l'écran.

Indications

Celui qui envoie le message est celui qui affecte le String au champ « msg » du « Zero » passé en argument.

Solution
class Zero {
  //insérez ici le contenu de notre ancien corps
  var msg = "Pas de Message"
  def afficherMessage = println(msg)
  def envoyerMessage(nouveauMsg: String, z: Zero) = z.msg = nouveauMessage
}
{
  val mateo = new Zero
  val cam = new Zero
  mateo envoyerMessage("Bonjour !", cam)
  cam.afficherMessage
}

J'ai mis les quatre instructions dans un bloc pour ne pas avoir l'affichage des références des deux instances. Le bloc est évalué à sa dernière ligne qui, elle, est de type Unit. Donc seul ce que va afficher println dans afficherMessage apparaîtra à l'écran, on aura donc un affichage plus propre.

Voici un autre chapitre terminé. :soleil: Vous êtes maintenant presque capables de faire des classes qui se respectent. Vous avez sûrement appris un grand nombre de nouvelles notions intéressantes telles que les méthodes, les arguments et, sûrement, l'écriture du code dans un fichier. ^^

Le problème des méthodes qu'on a faites est qu'elles sont un peu limitées. Les boucles, qu'on verra dans le chapitre suivant, permettront de faire des programmes très respectables. ;)

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